mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-10-05 09:48:11 -07:00
Compare commits
210 Commits
26-04-19
...
8b2f38307b
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b2f38307b | |||
| 3863d5a4ed | |||
| 94574c485a | |||
| f857926ec3 | |||
| adf4cccd4a | |||
| bf8e194685 | |||
| a2e508208c | |||
| f5888609a8 | |||
| c6cd72c528 | |||
| 82e0c053d0 | |||
| 3b29227dbc | |||
| ce2f93d234 | |||
| 13a171f199 | |||
| 3da352488e | |||
| 2386047c2f | |||
| baa550037a | |||
| b53447bed5 | |||
| 4959a58de3 | |||
| 9141ff453b | |||
| f955ab984c | |||
| 435b9ce9aa | |||
| 1ec7528dac | |||
| e7822aad8e | |||
| 8140e83e46 | |||
| cc0899290e | |||
| 3012139028 | |||
| c590b87cff | |||
| 5cabd026ae | |||
| 7ccd938f9b | |||
| 88a210e82e | |||
| 74e619df37 | |||
| 10a97b9c63 | |||
| 159043803c | |||
| bd2fbfcb67 | |||
| 64600826b7 | |||
| 80deb4bbbd | |||
| 00c8bb8ce6 | |||
| 09ed8b948c | |||
| e2daefbb6a | |||
| 39bf17aafe | |||
| 4836cd94ab | |||
| 9d41ca2515 | |||
| 18421ffbfe | |||
| 3f0921d983 | |||
| ed7318c270 | |||
| 623e1e3998 | |||
| 558e3d0cb3 | |||
| d0db547dd0 | |||
| be4bb00390 | |||
| a8cc85531c | |||
| 2dea0d27bf | |||
| 61a0220c1a | |||
| c080bbe301 | |||
| e1d1b39567 | |||
| 2aae8ad9ca | |||
| 1b6d1af951 | |||
| 5c3617c05f | |||
| a9a943fbfe | |||
| b094a9e352 | |||
| 4bd18afb12 | |||
| 0b24e703a1 | |||
| 1a1be7a393 | |||
| a2d8f60d12 | |||
| 5d25fdb035 | |||
| c255e3a1db | |||
| d7c144646f | |||
| 27077aa21f | |||
| 632dcf7b41 | |||
| a25bbdc9c9 | |||
| 47d886306e | |||
| edda658fa4 | |||
| 1ab20f1b56 | |||
| 0e9093a74b | |||
| f52eaea76c | |||
| c3fcf2f417 | |||
| 8d04eba21d | |||
| 8d42c0ce77 | |||
| 829503568e | |||
| 746fdb5409 | |||
| 67da892fe9 | |||
| dc191487e0 | |||
| 5c0c405800 | |||
| 60166e6ba1 | |||
| cb0b59600a | |||
| b9f3be4df2 | |||
| cf86fbd238 | |||
| 3fdb369128 | |||
| db7defff5a | |||
| 451cbec0b9 | |||
| 5c69e295ab | |||
| 5065a92d55 | |||
| 5483e8e2c0 | |||
| e8949a2612 | |||
| db71a0b24d | |||
| d3d5422768 | |||
| 5185f753e5 | |||
| eb897d0f1b | |||
| 50fc80a1c3 | |||
| 301e15c44d | |||
| b6f886a7c5 | |||
| 0b085286d3 | |||
| 29d3d9bb52 | |||
| bf05a3ef30 | |||
| 6280ac3b0a | |||
| cc9df27569 | |||
| ac51acdafe | |||
| 6fec169347 | |||
| 4af006e869 | |||
| 256ef4d341 | |||
| 698f2ffc2c | |||
| 4879a35eb8 | |||
| fd7e850789 | |||
| 0cf07c38da | |||
| 1f073b2d75 | |||
| 3037214932 | |||
| 18d6d9783a | |||
| 267add3227 | |||
| a170627f52 | |||
| 5c244eaca7 | |||
| 6bcadccf09 | |||
| ccb3bac48c | |||
| 0a7ecf82a9 | |||
| 1c6929dd84 | |||
| 1d3d9040f2 | |||
| 8f1069628e | |||
| f8c585a9d1 | |||
| 9651051990 | |||
| a54a62d0d7 | |||
| 100caa0f5c | |||
| bb87aa2944 | |||
| 2e2968d1bc | |||
| 0457262472 | |||
| f23c359114 | |||
| 81e74dcb97 | |||
| 8ead941c6c | |||
| ece03cabba | |||
| 4b7f68f920 | |||
| 2dae86a6f2 | |||
| 85058c2156 | |||
| ea2f4c5572 | |||
| 48033816a8 | |||
| b517ef3182 | |||
| 6fef16353e | |||
| 84ea3f9e8e | |||
| 6bb6b0a301 | |||
| 465d6c6c18 | |||
| ef384e85dd | |||
| 7ee04879e2 | |||
| 4c73200f58 | |||
| 31aabe9786 | |||
| 4ea55a61b8 | |||
| 0209b80a22 | |||
| 0511dfb6ca | |||
| b558df3340 | |||
| 89602cfcab | |||
| b4557993c9 | |||
| f119d7c988 | |||
| b3d8d0aea9 | |||
| a977cba772 | |||
| 489d40d87d | |||
| 3a468a9a3b | |||
| a2e220d3f4 | |||
| febc02b50b | |||
| 311404f56b | |||
| d929cdf7c8 | |||
| 2b930ad95f | |||
| 3c60f3966b | |||
| c59d399ab8 | |||
| f69e40fa26 | |||
| e9dcc5717e | |||
| 5bec3d5db7 | |||
| 6ec2b47345 | |||
| ebda16beae | |||
| c29a26f183 | |||
| 2014e9cf75 | |||
| 33f2e1d4b7 | |||
| eb037542b4 | |||
| 039b42ad53 | |||
| 33b4718744 | |||
| 71ba9b6b47 | |||
| 1957917270 | |||
| b9ccddf47d | |||
| cd0ba51b5a | |||
| 1f23d88c7a | |||
| 46e43ab09c | |||
| 96950b6b4e | |||
| 5f94ef4478 | |||
| c4193eddc5 | |||
| c8962a0e77 | |||
| b6a749e5db | |||
| 7dbbe9809e | |||
| 79a3e32d5d | |||
| c5a4e954f9 | |||
| 7b862768b0 | |||
| af8d8dae9f | |||
| d9d5823fdb | |||
| 82dda33800 | |||
| 3876e38636 | |||
| 6bc1357d6a | |||
| 37218e7fe0 | |||
| 678e11eade | |||
| f72313fe45 | |||
| 06374ef78a | |||
| bdde1ec6b8 | |||
| d2176fe4f0 | |||
| fb9eef904a | |||
| 48903998a5 | |||
| f2dca0265f | |||
| 1147ed9858 | |||
| 19d93f4ffd |
@@ -0,0 +1,46 @@
|
||||
# Normalize every text file to LF in the repository.
|
||||
# The build runs under Linux/MinGW containers, so LF is also used in the
|
||||
# working tree; Windows editors and toolchains handle LF fine.
|
||||
* text=auto eol=lf
|
||||
|
||||
# Windows-only files that must keep CRLF in the working tree.
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.sln text eol=crlf
|
||||
*.vcproj text eol=crlf
|
||||
*.vcxproj text eol=crlf
|
||||
*.props text eol=crlf
|
||||
*.filters text eol=crlf
|
||||
|
||||
# Files that must keep LF even if a Windows editor rewrites them.
|
||||
*.sh text eol=lf
|
||||
*.in text eol=lf
|
||||
*.cmake text eol=lf
|
||||
*.mk text eol=lf
|
||||
Makefile text eol=lf
|
||||
Dockerfile text eol=lf
|
||||
|
||||
# Binary files - never touch the contents.
|
||||
*.bin binary
|
||||
*.ico binary
|
||||
*.ttf binary
|
||||
*.otf binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.bmp binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.gz binary
|
||||
*.dll binary
|
||||
*.exe binary
|
||||
*.lib binary
|
||||
*.a binary
|
||||
*.o binary
|
||||
*.obj binary
|
||||
*.pdb binary
|
||||
|
||||
# Vendored code is stored and checked out byte-for-byte as upstream ships it,
|
||||
# so re-importing a library never produces line-ending-only diffs.
|
||||
src/spice2x/external/** -text
|
||||
@@ -11,9 +11,7 @@ assignees: ''
|
||||
*name of game, version of game*
|
||||
|
||||
## Version of spice2x
|
||||
*version of spice, this can be seen in the About tab, and in log.txt*
|
||||
|
||||
*please remember to test the LATEST beta version before posting a bug about it**
|
||||
*please remember to test the LATEST beta version before posting a bug about it*
|
||||
|
||||
## Describe the issue
|
||||
*what's the issue?*
|
||||
@@ -22,3 +20,7 @@ assignees: ''
|
||||
> [!WARNING]
|
||||
> Please make sure you remove any personally identifiable information from the log file.
|
||||
> Please set `-loglevel` to `all` before launching the game for more verbose logs.
|
||||
|
||||
## A note on issue management
|
||||
|
||||
`Closed as not planned` means exactly that - it's closed, because it's not on our plans to address them any time soon. It doesn't mean your issue is invalid, it just means it's not a priority, and we can always revisit them later.
|
||||
|
||||
+31
-31
@@ -1,32 +1,32 @@
|
||||
on: [push, pull_request]
|
||||
name: Continuous Integration
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fw-ci:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./src/spice2x
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set ccache environment variables
|
||||
run: |
|
||||
echo "CCACHE_DIR=${{ github.workspace }}/src/spice2x/.ccache" >> $GITHUB_ENV
|
||||
- name: Install ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
- name: Calculate commit SHA
|
||||
id: vars
|
||||
run: |
|
||||
calculatedSha=$(git rev-parse --short ${{ github.sha }})
|
||||
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
|
||||
- name: Compile
|
||||
run: ./build_docker.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: spice2x-ci-${{ env.COMMIT_SHORT_SHA }}
|
||||
path: src/spice2x/bin
|
||||
on: [push, pull_request]
|
||||
name: Continuous Integration
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fw-ci:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./src/spice2x
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set ccache environment variables
|
||||
run: |
|
||||
echo "CCACHE_DIR=${{ github.workspace }}/src/spice2x/.ccache" >> $GITHUB_ENV
|
||||
- name: Install ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.23
|
||||
- name: Calculate commit SHA
|
||||
id: vars
|
||||
run: |
|
||||
calculatedSha=$(git rev-parse --short ${{ github.sha }})
|
||||
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
|
||||
- name: Compile
|
||||
run: ./build_docker.sh
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: spice2x-ci-${{ env.COMMIT_SHORT_SHA }}
|
||||
path: src/spice2x/bin
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Draft Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build and Draft Release
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./src/spice2x
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
- name: Clean leftover build artifacts
|
||||
run: |
|
||||
rm -rf .ccache dist bin cmake-build-*
|
||||
- name: Compile
|
||||
run: ./build_docker.sh
|
||||
- name: Determine release name from dist filename
|
||||
run: |
|
||||
dist=$(basename "$(ls dist/spice2x-*.zip | grep -v -- '-full.zip')")
|
||||
# strip the ".zip" to get the base name, e.g. spice2x-26-06-28
|
||||
name="${dist%.zip}"
|
||||
# the tag is the date portion, e.g. 26-06-28
|
||||
tag="${name#spice2x-}"
|
||||
echo "RELEASE_NAME=$name" >> $GITHUB_ENV
|
||||
echo "RELEASE_TAG=$tag" >> $GITHUB_ENV
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
draft: true
|
||||
prerelease: true
|
||||
tag_name: ${{ env.RELEASE_TAG }}
|
||||
name: ${{ env.RELEASE_NAME }}
|
||||
target_commitish: main
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
src/spice2x/dist/spice2x-*.zip
|
||||
+7
-3
@@ -11,13 +11,17 @@ To contribute, fork the repo (just the main branch), make changes in your fork,
|
||||
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
|
||||
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
|
||||
|
||||
### Forbidden "features" that will never be accepted
|
||||
### Pull requests that will not be accepted
|
||||
|
||||
* Performing decryption of encrypted files
|
||||
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data
|
||||
* Performing decryption of encrypted files.
|
||||
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data.
|
||||
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
|
||||
* This includes automatic updaters.
|
||||
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
|
||||
* Built-in EA server that saves data. They should live in a separate project.
|
||||
* Any additions to built-in patches (modifications / removals are OK if we have a good reason).
|
||||
* Changes to the software license terms.
|
||||
* Localization / translation for UI text. It becomes too difficult to manage by the maintainers.
|
||||
|
||||
### Avoiding regressions
|
||||
|
||||
|
||||
+195
-25
@@ -235,6 +235,13 @@ add_compile_definitions(
|
||||
_WIN32_IE=0x0400
|
||||
)
|
||||
|
||||
# SPICE_XP: set by build_all.sh for the WinXP-compat toolchains. Gates out
|
||||
# the DX11 overlay backend
|
||||
option(SPICE_XP "Build for the WinXP-compat toolchain" OFF)
|
||||
if(SPICE_XP)
|
||||
add_compile_definitions(SPICE_XP=1)
|
||||
endif()
|
||||
|
||||
# acioemu log
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DACIOEMU_LOG")
|
||||
|
||||
@@ -249,6 +256,28 @@ add_subdirectory(external/imgui EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(external/minhook EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(external/cpu_features EXCLUDE_FROM_ALL)
|
||||
|
||||
# libjpeg-turbo's SIMD needs nasm; enable_language belongs at top level. Without
|
||||
# it the library still builds, just without SIMD.
|
||||
include(CheckLanguage)
|
||||
check_language(ASM_NASM)
|
||||
if(CMAKE_ASM_NASM_COMPILER)
|
||||
enable_language(ASM_NASM)
|
||||
else()
|
||||
message(WARNING
|
||||
"nasm not found: libjpeg-turbo will be built without SIMD, "
|
||||
"which makes api screen capture several times slower")
|
||||
endif()
|
||||
add_subdirectory(external/libjpeg-turbo EXCLUDE_FROM_ALL)
|
||||
|
||||
# fpng's SIMD needs the whole unit built for SSE4.1, which its runtime CPU check
|
||||
# cannot undo, so keep it scalar rather than raising the CPU baseline
|
||||
set_source_files_properties(external/fpng/fpng.cpp PROPERTIES
|
||||
COMPILE_DEFINITIONS "FPNG_NO_SSE=1")
|
||||
if(NOT MSVC)
|
||||
set_source_files_properties(external/fpng/fpng.cpp PROPERTIES
|
||||
COMPILE_OPTIONS "-fno-strict-aliasing")
|
||||
endif()
|
||||
|
||||
# set link time optimizations (disabled for Debug builds for speed, disabled
|
||||
# for RelWithDebInfo builds due to "lto1: error: two or more sections for"
|
||||
# errors)
|
||||
@@ -325,6 +354,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
api/modules/control.cpp
|
||||
api/modules/touch.cpp
|
||||
api/modules/iidx.cpp
|
||||
api/modules/sdvx.cpp
|
||||
api/serial.cpp
|
||||
api/modules/drs.cpp
|
||||
api/modules/lcd.cpp
|
||||
@@ -372,7 +402,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
external/tinyxml2/tinyxml2.cpp
|
||||
external/http-parser/http_parser.c
|
||||
external/usbhidusage/usb-hid-usage.c
|
||||
external/toojpeg/toojpeg.cpp
|
||||
external/fpng/fpng.cpp
|
||||
external/scard/scard.cpp
|
||||
|
||||
# games
|
||||
@@ -401,14 +431,19 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/iidx/mf_wrappers.cpp
|
||||
games/sdvx/bi2x_hook.cpp
|
||||
games/sdvx/sdvx.cpp
|
||||
games/sdvx/sdvx_live2d.cpp
|
||||
games/sdvx/io.cpp
|
||||
games/sdvx/camera.cpp
|
||||
games/jb/bi2x_hook.cpp
|
||||
games/jb/jb.cpp
|
||||
games/jb/jb_touch.cpp
|
||||
games/jb/io.cpp
|
||||
games/nost/nost.cpp
|
||||
games/nost/io.cpp
|
||||
games/nost/poke.cpp
|
||||
games/nost/touch_mode.cpp
|
||||
games/gitadora/gitadora.cpp
|
||||
games/gitadora/asio.cpp
|
||||
games/gitadora/io.cpp
|
||||
games/gitadora/handle.cpp
|
||||
games/gitadora/j32d.cpp
|
||||
@@ -422,6 +457,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/rb/rb.cpp
|
||||
games/rb/io.cpp
|
||||
games/rb/touch.cpp
|
||||
games/rb/touch_debug.cpp
|
||||
games/bs/bs.cpp
|
||||
games/bs/io.cpp
|
||||
games/rf3d/rf3d.cpp
|
||||
@@ -486,16 +522,24 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
# hooks
|
||||
hooks/audio/acm.cpp
|
||||
hooks/audio/audio.cpp
|
||||
hooks/audio/asio_driver_scan.cpp
|
||||
hooks/audio/asio_proxy.cpp
|
||||
hooks/audio/buffer.cpp
|
||||
hooks/audio/mme.cpp
|
||||
hooks/audio/util.cpp
|
||||
hooks/audio/xact.cpp
|
||||
hooks/audio/backends/dsound/dsound_backend.cpp
|
||||
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
|
||||
hooks/audio/backends/mmdevice/device.cpp
|
||||
hooks/audio/backends/mmdevice/device_collection.cpp
|
||||
hooks/audio/backends/mmdevice/device_enumerator.cpp
|
||||
hooks/audio/backends/mmdevice/null_device.cpp
|
||||
hooks/audio/backends/mmdevice/null_discard_backend.cpp
|
||||
hooks/audio/backends/wasapi/audio_client.cpp
|
||||
hooks/audio/backends/wasapi/audio_render_client.cpp
|
||||
hooks/audio/backends/wasapi/downmix.cpp
|
||||
hooks/audio/backends/wasapi/resample.cpp
|
||||
hooks/audio/backends/wasapi/shared.cpp
|
||||
hooks/audio/backends/wasapi/dummy_audio_client.cpp
|
||||
hooks/audio/backends/wasapi/dummy_audio_clock.cpp
|
||||
hooks/audio/backends/wasapi/dummy_audio_render_client.cpp
|
||||
@@ -510,19 +554,32 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
hooks/devicehook.cpp
|
||||
hooks/graphics/graphics.cpp
|
||||
hooks/graphics/graphics_windowed.cpp
|
||||
hooks/graphics/jpeg_encoder.cpp
|
||||
hooks/graphics/nvapi_impl.cpp
|
||||
hooks/graphics/nvapi_hook.cpp
|
||||
hooks/graphics/nvenc_hook.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_backend.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_readback.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_screenshot.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_device.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_gfdm.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_live2d.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_swapchain.cpp
|
||||
hooks/graphics/backends/d3d9/d3d9_texture.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_backend.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_swapchain.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_factory.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_vtable_capture.cpp
|
||||
hooks/graphics/backends/d3d11/d3d11_screenshot.cpp
|
||||
hooks/input/dinput8/fake_backend.cpp
|
||||
hooks/input/dinput8/fake_device.cpp
|
||||
hooks/input/dinput8/hook.cpp
|
||||
hooks/lang.cpp
|
||||
hooks/libraryhook.cpp
|
||||
hooks/networkhook.cpp
|
||||
hooks/icmphook_net.cpp
|
||||
hooks/icmphook_iphlpapi.cpp
|
||||
hooks/powrprof.cpp
|
||||
#hooks/rom.cpp
|
||||
hooks/setupapihook.cpp
|
||||
@@ -545,7 +602,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
misc/device.cpp
|
||||
misc/eamuse.cpp
|
||||
misc/extdev.cpp
|
||||
misc/nativetouchhook.cpp
|
||||
misc/hotkeys.cpp
|
||||
misc/sciunit.cpp
|
||||
misc/sde.cpp
|
||||
misc/wintouchemu.cpp
|
||||
@@ -556,6 +613,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
|
||||
# overlay
|
||||
overlay/overlay.cpp
|
||||
overlay/notifications.cpp
|
||||
overlay/window.cpp
|
||||
overlay/imgui/extensions.cpp
|
||||
overlay/imgui/impl_spice.cpp
|
||||
@@ -582,12 +640,27 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
overlay/windows/keypad.cpp
|
||||
overlay/windows/log.cpp
|
||||
overlay/windows/midi.cpp
|
||||
overlay/windows/nostalgia_touch_piano.cpp
|
||||
overlay/windows/obs.cpp
|
||||
overlay/windows/obs_websocket.cpp
|
||||
overlay/windows/patch_manager.cpp
|
||||
overlay/windows/popn_sub.cpp
|
||||
overlay/windows/wnd_manager.cpp
|
||||
|
||||
# patcher
|
||||
patcher/config.cpp
|
||||
patcher/lifecycle.cpp
|
||||
patcher/loader.cpp
|
||||
patcher/loader_group.cpp
|
||||
patcher/runtime.cpp
|
||||
|
||||
# external
|
||||
external/easywsclient/easywsclient.cpp
|
||||
|
||||
# rawinput
|
||||
rawinput/rawinput.cpp
|
||||
rawinput/rawinput_handles.cpp
|
||||
rawinput/midi.cpp
|
||||
rawinput/sextet.cpp
|
||||
rawinput/piuio.cpp
|
||||
rawinput/touch.cpp
|
||||
@@ -607,12 +680,21 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
reader/structuredmessage.cpp
|
||||
reader/crypt.cpp
|
||||
|
||||
# sdk
|
||||
sdk/sdk.cpp
|
||||
|
||||
# stubs
|
||||
stubs/stubs.cpp
|
||||
|
||||
# touch
|
||||
touch/gdi_overlay.cpp
|
||||
touch/native/inject.cpp
|
||||
touch/native/inject_mouse.cpp
|
||||
touch/native/inject_synthetic.cpp
|
||||
touch/native/nativetouchhook.cpp
|
||||
touch/native/transform.cpp
|
||||
touch/touch.cpp
|
||||
touch/touch_indicators.cpp
|
||||
touch/touch_gestures.cpp
|
||||
touch/win7.cpp
|
||||
touch/win8.cpp
|
||||
|
||||
@@ -633,6 +715,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
util/time.cpp
|
||||
util/cpuutils.cpp
|
||||
util/netutils.cpp
|
||||
util/precise_timer.cpp
|
||||
util/sysutils.cpp
|
||||
util/lz77.cpp
|
||||
util/tapeled.cpp
|
||||
@@ -644,37 +727,74 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
|
||||
|
||||
# guard against statically importing DLLs that must always be loaded dynamically:
|
||||
# * DLLs users override via the modules directory (e.g. DXVK's d3d9.dll) - a
|
||||
# static import loads the system copy at startup and preempts the override
|
||||
# (issue #779).
|
||||
# * Media Foundation DLLs (mf/mfplat/mfreadwrite) - a static import breaks
|
||||
# Unity games.
|
||||
# the check runs objdump on each produced binary and fails the build if any
|
||||
# forbidden DLL is imported.
|
||||
set(SPICE_FORBIDDEN_STATIC_IMPORTS
|
||||
d3d8.dll d3d9.dll d3d10core.dll d3d11.dll dxgi.dll opengl32.dll
|
||||
mf.dll mfplat.dll mfreadwrite.dll)
|
||||
|
||||
function(spice_guard_dll_imports target)
|
||||
if(MSVC)
|
||||
# objdump-based parsing assumes a GNU/LLVM toolchain
|
||||
return()
|
||||
endif()
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DOBJDUMP=${CMAKE_OBJDUMP}
|
||||
-DTARGET_FILE=$<TARGET_FILE:${target}>
|
||||
"-DFORBIDDEN=${SPICE_FORBIDDEN_STATIC_IMPORTS}"
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_no_static_dll_imports.cmake
|
||||
VERBATIM
|
||||
COMMENT "Checking ${target} for forbidden static DLL imports")
|
||||
endfunction()
|
||||
|
||||
# spice.exe / spice_laa.exe shared objects
|
||||
###########################################
|
||||
# spice.exe and spice_laa.exe are compiled identically; the only difference is
|
||||
# the large-address-aware bit, which is set at link time. Compile the sources
|
||||
# once into a shared OBJECT library so spice_laa.exe only costs an extra link
|
||||
# instead of a full recompile.
|
||||
|
||||
add_library(spicetools_spice_objs OBJECT ${SOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice_objs
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features jpeg-static)
|
||||
target_link_libraries(spicetools_spice_objs PUBLIC winscard)
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_spice_objs PROPERTIES COMPILE_FLAGS "-m32")
|
||||
endif()
|
||||
|
||||
# spice.exe
|
||||
###########
|
||||
|
||||
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
|
||||
add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
target_link_libraries(spicetools_spice PUBLIC winscard)
|
||||
add_executable(spicetools_spice ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice PRIVATE spicetools_spice_objs)
|
||||
set_target_properties(spicetools_spice PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice")
|
||||
|
||||
IF(NOT MSVC)
|
||||
set_target_properties(spicetools_spice PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
||||
set_target_properties(spicetools_spice PROPERTIES LINK_FLAGS "-m32")
|
||||
endif()
|
||||
|
||||
# spice_laa.exe
|
||||
###########
|
||||
|
||||
set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc)
|
||||
add_executable(spicetools_spice_laa ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice_laa
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
target_link_libraries(spicetools_spice_laa PUBLIC winscard)
|
||||
add_executable(spicetools_spice_laa ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice_laa PRIVATE spicetools_spice_objs)
|
||||
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice_laa PROPERTIES OUTPUT_NAME "spice_laa")
|
||||
target_compile_definitions(spicetools_spice_laa PRIVATE SPICE32_LARGE_ADDRESS_AWARE=1)
|
||||
|
||||
IF(NOT MSVC)
|
||||
set_target_properties(spicetools_spice_laa PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32 -Wl,--large-address-aware")
|
||||
set_target_properties(spicetools_spice_laa PROPERTIES LINK_FLAGS "-m32 -Wl,--large-address-aware")
|
||||
endif()
|
||||
|
||||
# spice_linux.exe
|
||||
@@ -684,7 +804,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W
|
||||
add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice_linux
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features jpeg-static)
|
||||
set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux")
|
||||
target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
|
||||
@@ -702,11 +822,15 @@ add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
|
||||
target_link_libraries(spicetools_spice64
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features jpeg-static)
|
||||
target_link_libraries(spicetools_spice64 PUBLIC winscard)
|
||||
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
|
||||
target_compile_definitions(spicetools_spice64 PRIVATE SPICE64=1)
|
||||
if(NOT SPICE_XP)
|
||||
target_link_libraries(spicetools_spice64 PUBLIC d3d11 dxgi)
|
||||
target_compile_definitions(spicetools_spice64 PRIVATE SPICE_D3D11=1)
|
||||
endif()
|
||||
|
||||
IF(NOT MSVC)
|
||||
set_target_properties(spicetools_spice64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||
@@ -721,11 +845,14 @@ add_executable(spicetools_spice64_linux ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
|
||||
target_link_libraries(spicetools_spice64_linux
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features jpeg-static)
|
||||
set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux")
|
||||
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1)
|
||||
target_compile_definitions(spicetools_spice64_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
|
||||
# spice64_linux is never built under the WinXP toolchain, so dx11 is always on.
|
||||
target_link_libraries(spicetools_spice64_linux PUBLIC d3d11 dxgi)
|
||||
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE_D3D11=1)
|
||||
|
||||
IF(NOT MSVC)
|
||||
set_target_properties(spicetools_spice64_linux PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||
@@ -738,8 +865,8 @@ set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp)
|
||||
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
|
||||
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_cfg
|
||||
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features jpeg-static)
|
||||
target_link_libraries(spicetools_cfg PUBLIC winscard)
|
||||
set_target_properties(spicetools_cfg PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg")
|
||||
@@ -756,8 +883,8 @@ set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp)
|
||||
set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc)
|
||||
add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_cfg_linux
|
||||
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features jpeg-static)
|
||||
set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux")
|
||||
target_compile_definitions(spicetools_cfg_linux PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
|
||||
@@ -860,6 +987,36 @@ if(NOT MSVC)
|
||||
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_flat_c.dll (32 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
|
||||
add_library(spicetools_sdk_sample_v0_flat_c_32 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_32 PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_flat_c.dll (64 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/flat_c/v0_flat_c.c)
|
||||
add_library(spicetools_sdk_sample_v0_flat_c_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/flat_c/v0_flat_c.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_flat_c")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_flat_c_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||
endif()
|
||||
|
||||
# sdk_sample_v0_cpp.dll (64 bit)
|
||||
set(SOURCE_FILES sdk/sample/v0/cpp/v0_cpp.cpp)
|
||||
add_library(spicetools_sdk_sample_v0_cpp_64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} sdk/sample/v0/cpp/v0_cpp.def)
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES OUTPUT_NAME "sdk_sample_v0_cpp")
|
||||
|
||||
if(NOT MSVC)
|
||||
set_target_properties(spicetools_sdk_sample_v0_cpp_64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
|
||||
endif()
|
||||
|
||||
# output directories
|
||||
####################
|
||||
|
||||
@@ -869,15 +1026,28 @@ set_target_properties(spicetools_cfg spicetools_cfg_linux
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools")
|
||||
|
||||
# output 32bit
|
||||
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm
|
||||
set_target_properties(spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_kbt spicetools_stubs_kld spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32
|
||||
PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive32"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/32")
|
||||
|
||||
# output 64bit
|
||||
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64
|
||||
set_target_properties(spicetools_spice64 spicetools_spice64_linux spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvcuda spicetools_stubs_nvcuvid spicetools_stubs_nvEncodeAPI64 spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64
|
||||
PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/archive64"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/spicetools/64")
|
||||
|
||||
# forbidden static DLL import guard
|
||||
###################################
|
||||
# apply the check to every executable and DLL produced by this project.
|
||||
get_property(spice_all_targets DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY BUILDSYSTEM_TARGETS)
|
||||
foreach(spice_target IN LISTS spice_all_targets)
|
||||
get_target_property(spice_target_type ${spice_target} TYPE)
|
||||
if(spice_target_type STREQUAL "EXECUTABLE"
|
||||
OR spice_target_type STREQUAL "SHARED_LIBRARY"
|
||||
OR spice_target_type STREQUAL "MODULE_LIBRARY")
|
||||
spice_guard_dll_imports(${spice_target})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
@@ -267,6 +267,20 @@ which also means that your hex edits are applicable directly.
|
||||
- `Side Panel Right Inner`
|
||||
- `Side Panel Right`
|
||||
|
||||
#### SDVX
|
||||
- tapeled_get(name: str, ...)
|
||||
- returns a list containing a dict of the current tape LED states. The dict keys are:
|
||||
- `Title`
|
||||
- `Upper Left Speaker`
|
||||
- `Upper Right Speaker`
|
||||
- `Left Wing`
|
||||
- `Right Wing`
|
||||
- `Control Panel`
|
||||
- `Lower Left Speaker`
|
||||
- `Lower Right Speaker`
|
||||
- `Woofer`
|
||||
- `V Unit`
|
||||
|
||||
#### LCD
|
||||
- info()
|
||||
- returns information about the serial LCD controller some games use
|
||||
@@ -283,6 +297,16 @@ Python is the only one that is fully spec compliant.
|
||||
Other libraries may be missing features and contain bugs; please feel free to
|
||||
contribute code to fill the gaps if you work on a project using these libraries.
|
||||
|
||||
## Spice SDK (for DLL hooks / plugins)
|
||||
|
||||
As an alternative to Spice API, Spice SDK is available.
|
||||
|
||||
This is a flat C, header-only library that can be included in your DLL. Once
|
||||
initialized, you can call directly into helper routines provided by Spicetools
|
||||
executable, making low-latency and high-throughput interactions possible.
|
||||
|
||||
Check the documentation on the wiki for more details.
|
||||
|
||||
## License
|
||||
Unless otherwise noted, all files are licensed under the GPLv3.
|
||||
See the LICENSE file for the full license text.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "touch/native/nativetouchhook.h"
|
||||
|
||||
// static stuff
|
||||
static int ACIO_WARMUP = 0;
|
||||
@@ -140,8 +141,9 @@ static int __cdecl ac_io_update(int a1) {
|
||||
// flush device output
|
||||
RI_MGR->devices_flush_output();
|
||||
|
||||
// update wintouchemu
|
||||
// update touch emulation
|
||||
wintouchemu::update();
|
||||
nativetouch::refresh_contact_lifetime();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#include "mdxf.h"
|
||||
#include "mdxf_poll.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
#include "games/ddr/io.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include <mutex>
|
||||
|
||||
#define MDFX_DEBUG_VERBOSE 0
|
||||
|
||||
@@ -108,9 +111,10 @@ static void mdxf_thread_start() {
|
||||
MDXF_THREAD = std::thread([] {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
|
||||
mdxf_poll(false);
|
||||
std::this_thread::sleep_for(THREAD_PERIOD);
|
||||
timer.sleep(THREAD_PERIOD);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -357,14 +361,17 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
|
||||
// decide on button map
|
||||
const size_t *button_map = nullptr;
|
||||
int player = 0;
|
||||
switch (node) {
|
||||
case 17:
|
||||
case 25:
|
||||
button_map = &buttons_p1[0];
|
||||
player = 1;
|
||||
break;
|
||||
case 18:
|
||||
case 26:
|
||||
button_map = &buttons_p2[0];
|
||||
player = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -374,19 +381,27 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFP
|
||||
if (source == EXTERNAL_POLL) {
|
||||
// get buttons
|
||||
auto &buttons = games::ddr::get_buttons();
|
||||
|
||||
// get analogs
|
||||
bool analog_left = false;
|
||||
bool analog_right = false;
|
||||
games::ddr::get_analog_x_axis(player, analog_left, analog_right);
|
||||
bool analog_up = false;
|
||||
bool analog_down = false;
|
||||
games::ddr::get_analog_y_axis(player, analog_up, analog_down);
|
||||
|
||||
uint8_t up_down = 0;
|
||||
uint8_t left_right = 0;
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0])) || analog_up) {
|
||||
up_down |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1])) || analog_down) {
|
||||
up_down |= 0x0F;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2])) || analog_left) {
|
||||
left_right |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3])) || analog_right) {
|
||||
left_right |= 0x0F;
|
||||
}
|
||||
current_state = (uint16_t(up_down) << 8) | left_right;
|
||||
@@ -500,4 +515,4 @@ acio::MDXFModule::~MDXFModule() {
|
||||
if (IS_THREAD_NEEDED) {
|
||||
mdxf_thread_stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "games/nost/io.h"
|
||||
#include "games/nost/nost.h"
|
||||
#include "games/nost/touch_mode.h"
|
||||
#include "util/logging.h"
|
||||
#include "avs/game.h"
|
||||
|
||||
@@ -16,6 +17,7 @@ using namespace GameAPI;
|
||||
// static stuff
|
||||
static uint8_t STATUS_BUFFER[277];
|
||||
static bool STATUS_BUFFER_FREEZE = false;
|
||||
static constexpr uint8_t NOST_TOUCH_PIANO_VELOCITY = 11;
|
||||
|
||||
/*
|
||||
* Implementations
|
||||
@@ -226,6 +228,8 @@ static bool __cdecl ac_io_panb_update_control_status_buffer() {
|
||||
games::nost::Analogs::Key27, games::nost::Analogs::Key28,
|
||||
};
|
||||
|
||||
const auto touch_key_state = games::nost::touch_mode::piano_key_state();
|
||||
|
||||
// iterate pairs of keys
|
||||
for (size_t key_pair = 0; key_pair < 28 / 2; key_pair++) {
|
||||
|
||||
@@ -257,6 +261,10 @@ static bool __cdecl ac_io_panb_update_control_status_buffer() {
|
||||
if (button0 > 0) {
|
||||
state0 = button0;
|
||||
}
|
||||
if ((touch_key_state & (UINT32_C(1) << (key_pair * 2))) &&
|
||||
state0 < NOST_TOUCH_PIANO_VELOCITY) {
|
||||
state0 = NOST_TOUCH_PIANO_VELOCITY;
|
||||
}
|
||||
|
||||
const auto button1 = panb_get_button_velocity(
|
||||
buttons.at(button_mapping[key_pair * 2 + 1]),
|
||||
@@ -267,6 +275,10 @@ static bool __cdecl ac_io_panb_update_control_status_buffer() {
|
||||
if (button1 > 0) {
|
||||
state1 = button1;
|
||||
}
|
||||
if ((touch_key_state & (UINT32_C(1) << (key_pair * 2 + 1))) &&
|
||||
state1 < NOST_TOUCH_PIANO_VELOCITY) {
|
||||
state1 = NOST_TOUCH_PIANO_VELOCITY;
|
||||
}
|
||||
|
||||
// build value
|
||||
uint8_t value = 0;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "util/circular_buffer.h"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
using namespace acioemu;
|
||||
@@ -46,11 +47,12 @@ ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count)
|
||||
// keypad thread for faster polling
|
||||
if (keypad_thread) {
|
||||
this->keypad_thread = new std::thread([this]() {
|
||||
timeutils::PreciseSleepTimer timer;
|
||||
while (this->cards) {
|
||||
for (int unit = 0; unit < this->node_count; unit++) {
|
||||
this->update_keypad(unit, false);
|
||||
}
|
||||
Sleep(7);
|
||||
timer.sleep(7);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -237,7 +239,7 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
|
||||
// SDVX Old cabinet mode
|
||||
if (avs::game::is_model("KFC") && avs::game::SPEC[0] != 'G' && avs::game::SPEC[0] != 'H')
|
||||
answer_type = 1;
|
||||
if (avs::game::is_model("L44"))
|
||||
if (avs::game::is_model({ "L44", "T44" }))
|
||||
answer_type = 2;
|
||||
|
||||
// check answer type
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace api {
|
||||
|
||||
extern std::atomic_uint32_t CLIENT_COUNT;
|
||||
|
||||
inline bool has_clients() {
|
||||
return CLIENT_COUNT.load(std::memory_order_relaxed) > 0;
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,15 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "client.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "util/crypt.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "overlay/notifications.h"
|
||||
|
||||
#include "module.h"
|
||||
#include "modules/analogs.h"
|
||||
#include "modules/buttons.h"
|
||||
@@ -26,6 +29,7 @@
|
||||
#include "modules/lcd.h"
|
||||
#include "modules/lights.h"
|
||||
#include "modules/memory.h"
|
||||
#include "modules/sdvx.h"
|
||||
#include "modules/touch.h"
|
||||
#include "modules/resize.h"
|
||||
#include "request.h"
|
||||
@@ -34,6 +38,8 @@
|
||||
using namespace rapidjson;
|
||||
using namespace api;
|
||||
|
||||
std::atomic_uint32_t api::CLIENT_COUNT = 0;
|
||||
|
||||
Controller::Controller(unsigned short port, std::string password, bool pretty)
|
||||
: port(port), password(std::move(password)), pretty(pretty)
|
||||
{
|
||||
@@ -197,6 +203,9 @@ void Controller::connection_handler(api::ClientState client_state) {
|
||||
|
||||
// log connection
|
||||
log_info("api", "client connected: {}", client_address_str);
|
||||
overlay::notifications::add(
|
||||
overlay::notifications::Severity::Success,
|
||||
fmt::format("API client connected ({})", client_address_str));
|
||||
client_states_m.lock();
|
||||
client_states.emplace_back(&client_state);
|
||||
client_states_m.unlock();
|
||||
@@ -277,6 +286,9 @@ void Controller::connection_handler(api::ClientState client_state) {
|
||||
|
||||
// log disconnect
|
||||
log_info("api", "client disconnected: {}", client_address_str);
|
||||
overlay::notifications::add(
|
||||
overlay::notifications::Severity::Info,
|
||||
fmt::format("API client disconnected ({})", client_address_str));
|
||||
client_states_m.lock();
|
||||
client_states.erase(std::remove(client_states.begin(), client_states.end(), &client_state));
|
||||
client_states_m.unlock();
|
||||
@@ -403,8 +415,11 @@ void Controller::init_state(api::ClientState *state) {
|
||||
state->modules.push_back(new modules::LCD());
|
||||
state->modules.push_back(new modules::Lights());
|
||||
state->modules.push_back(new modules::Memory());
|
||||
state->modules.push_back(new modules::SDVX());
|
||||
state->modules.push_back(new modules::Touch());
|
||||
state->modules.push_back(new modules::Resize());
|
||||
|
||||
CLIENT_COUNT.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Controller::free_state(api::ClientState *state) {
|
||||
@@ -416,6 +431,8 @@ void Controller::free_state(api::ClientState *state) {
|
||||
|
||||
// free cipher
|
||||
delete state->cipher;
|
||||
|
||||
CLIENT_COUNT.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Controller::free_socket() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "games/io.h"
|
||||
#include "util/utils.h"
|
||||
#include "acio/mdxf/mdxf_poll.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -104,6 +105,7 @@ namespace api::modules {
|
||||
for (auto &analog : *this->analogs) {
|
||||
analog.override_enabled = false;
|
||||
}
|
||||
mdxf_poll(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -148,6 +150,7 @@ namespace api::modules {
|
||||
if (analog.getName() == name) {
|
||||
analog.override_state = CLAMP(state, 0.f, 1.f);
|
||||
analog.override_enabled = true;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -167,6 +170,7 @@ namespace api::modules {
|
||||
for (auto &analog : *this->analogs) {
|
||||
if (analog.getName() == name) {
|
||||
analog.override_enabled = false;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "launcher/launcher.h"
|
||||
#include "games/io.h"
|
||||
#include "util/utils.h"
|
||||
#include "acio/mdxf/mdxf_poll.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
@@ -107,6 +108,7 @@ namespace api::modules {
|
||||
for (auto &button : *this->buttons) {
|
||||
button.override_enabled = false;
|
||||
}
|
||||
mdxf_poll(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -153,6 +155,7 @@ namespace api::modules {
|
||||
GameAPI::Buttons::BUTTON_PRESSED : GameAPI::Buttons::BUTTON_NOT_PRESSED;
|
||||
button.override_velocity = CLAMP(state, 0.f, 1.f);
|
||||
button.override_enabled = true;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -172,6 +175,7 @@ namespace api::modules {
|
||||
for (auto &button : *this->buttons) {
|
||||
if (button.getName() == name) {
|
||||
button.override_enabled = false;
|
||||
mdxf_poll(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "capture.h"
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "util/crypt.h"
|
||||
@@ -14,6 +16,56 @@ namespace api::modules {
|
||||
|
||||
static thread_local std::vector<uint8_t> CAPTURE_BUFFER;
|
||||
|
||||
struct CachedFrame {
|
||||
std::vector<uint8_t> jpeg;
|
||||
uint64_t timestamp = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
static std::mutex FRAME_CACHE_M;
|
||||
static std::unordered_map<int, CachedFrame> FRAME_CACHE;
|
||||
|
||||
static void add_jpeg_response(
|
||||
int screen,
|
||||
uint64_t timestamp,
|
||||
int width,
|
||||
int height,
|
||||
const std::vector<uint8_t> &jpeg,
|
||||
Response &res) {
|
||||
|
||||
auto encoded = crypt::base64_encode(jpeg.data(), jpeg.size());
|
||||
|
||||
Value data;
|
||||
data.SetString(encoded.c_str(), encoded.length(), res.doc()->GetAllocator());
|
||||
res.add_data(timestamp);
|
||||
res.add_data(width);
|
||||
res.add_data(height);
|
||||
res.add_data(data);
|
||||
|
||||
std::lock_guard<std::mutex> lock(FRAME_CACHE_M);
|
||||
FRAME_CACHE[screen] = {jpeg, timestamp, width, height};
|
||||
}
|
||||
|
||||
static bool try_cached_response(int screen, Response &res) {
|
||||
std::lock_guard<std::mutex> lock(FRAME_CACHE_M);
|
||||
const auto pos = FRAME_CACHE.find(screen);
|
||||
if (pos == FRAME_CACHE.end() || pos->second.jpeg.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &cached = pos->second;
|
||||
auto encoded = crypt::base64_encode(cached.jpeg.data(), cached.jpeg.size());
|
||||
|
||||
Value data;
|
||||
data.SetString(encoded.c_str(), encoded.length(), res.doc()->GetAllocator());
|
||||
res.add_data(cached.timestamp);
|
||||
res.add_data(cached.width);
|
||||
res.add_data(cached.height);
|
||||
res.add_data(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
Capture::Capture() : Module("capture") {
|
||||
functions["get_screens"] = std::bind(&Capture::get_screens, this, _1, _2);
|
||||
functions["get_jpg"] = std::bind(&Capture::get_jpg, this, _1, _2);
|
||||
@@ -41,7 +93,6 @@ namespace api::modules {
|
||||
* reduce: uint for dividing image size
|
||||
*/
|
||||
void Capture::get_jpg(Request &req, Response &res) {
|
||||
CAPTURE_BUFFER.reserve(1024 * 128);
|
||||
|
||||
// settings
|
||||
int screen = 0;
|
||||
@@ -68,27 +119,17 @@ namespace api::modules {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
graphics_capture_trigger(screen);
|
||||
bool success = graphics_capture_receive_jpeg(screen, [] (uint8_t byte) {
|
||||
CAPTURE_BUFFER.push_back(byte);
|
||||
}, true, quality, true, divide, ×tamp, &width, &height);
|
||||
if (!success) {
|
||||
bool success = graphics_capture_receive_jpeg(
|
||||
screen, CAPTURE_BUFFER, quality, divide, ×tamp, &width, &height);
|
||||
|
||||
if (success) {
|
||||
add_jpeg_response(screen, timestamp, width, height, CAPTURE_BUFFER, res);
|
||||
CAPTURE_BUFFER.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// encode to base64
|
||||
auto encoded = crypt::base64_encode(
|
||||
CAPTURE_BUFFER.data(),
|
||||
CAPTURE_BUFFER.size());
|
||||
|
||||
// clear buffer
|
||||
// fall back to the last successful frame while the game is busy loading
|
||||
CAPTURE_BUFFER.clear();
|
||||
|
||||
// add data to response
|
||||
Value data;
|
||||
data.SetString(encoded.c_str(), encoded.length(), res.doc()->GetAllocator());
|
||||
res.add_data(timestamp);
|
||||
res.add_data(width);
|
||||
res.add_data(height);
|
||||
res.add_data(data);
|
||||
try_cached_response(screen, res);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
#include "ddr.h"
|
||||
#include <functional>
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
|
||||
namespace api::modules {
|
||||
|
||||
DDR::DDR() : Module("ddr") {
|
||||
functions["tapeled_get"] = std::bind(&DDR::tapeled_get, this, _1, _2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows fetching of the RGB LED strips that are gold cabinets, via SpiceAPI
|
||||
*/
|
||||
void DDR::tapeled_get(Request &req, Response &res) {
|
||||
static const char* device_names[11] = {
|
||||
"p1_foot_up",
|
||||
"p1_foot_right",
|
||||
"p1_foot_left",
|
||||
"p1_foot_down",
|
||||
"p2_foot_up",
|
||||
"p2_foot_right",
|
||||
"p2_foot_left",
|
||||
"p2_foot_down",
|
||||
"top_panel",
|
||||
"monitor_left",
|
||||
"monitor_right"
|
||||
};
|
||||
|
||||
Value response_object(kObjectType);
|
||||
|
||||
// Iterate through each device and dump its lights data into the response
|
||||
for (size_t device = 0; device < 11; device++) {
|
||||
size_t num_leds = 25;
|
||||
if (device > 7)
|
||||
num_leds = 50;
|
||||
|
||||
Value light_state(kArrayType);
|
||||
light_state.Reserve(num_leds * 3, res.doc()->GetAllocator());
|
||||
for (size_t led = 0; led < num_leds; led++) {
|
||||
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][0], res.doc()->GetAllocator());
|
||||
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][1], res.doc()->GetAllocator());
|
||||
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][2], res.doc()->GetAllocator());
|
||||
}
|
||||
|
||||
response_object.AddMember(StringRef(device_names[device]), light_state, res.doc()->GetAllocator());
|
||||
}
|
||||
|
||||
res.add_data(response_object);
|
||||
}
|
||||
}
|
||||
#include "ddr.h"
|
||||
#include <functional>
|
||||
#include "external/rapidjson/document.h"
|
||||
#include "games/ddr/ddr.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
|
||||
namespace api::modules {
|
||||
|
||||
DDR::DDR() : Module("ddr") {
|
||||
functions["tapeled_get"] = std::bind(&DDR::tapeled_get, this, _1, _2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows fetching of the RGB LED strips that are gold cabinets, via SpiceAPI
|
||||
*/
|
||||
void DDR::tapeled_get(Request &req, Response &res) {
|
||||
static const char* device_names[11] = {
|
||||
"p1_foot_up",
|
||||
"p1_foot_right",
|
||||
"p1_foot_left",
|
||||
"p1_foot_down",
|
||||
"p2_foot_up",
|
||||
"p2_foot_right",
|
||||
"p2_foot_left",
|
||||
"p2_foot_down",
|
||||
"top_panel",
|
||||
"monitor_left",
|
||||
"monitor_right"
|
||||
};
|
||||
|
||||
Value response_object(kObjectType);
|
||||
|
||||
// Iterate through each device and dump its lights data into the response
|
||||
for (size_t device = 0; device < 11; device++) {
|
||||
size_t num_leds = 25;
|
||||
if (device > 7)
|
||||
num_leds = 50;
|
||||
|
||||
Value light_state(kArrayType);
|
||||
light_state.Reserve(num_leds * 3, res.doc()->GetAllocator());
|
||||
for (size_t led = 0; led < num_leds; led++) {
|
||||
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][0], res.doc()->GetAllocator());
|
||||
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][1], res.doc()->GetAllocator());
|
||||
light_state.PushBack(games::ddr::DDR_TAPELEDS[device][led][2], res.doc()->GetAllocator());
|
||||
}
|
||||
|
||||
response_object.AddMember(StringRef(device_names[device]), light_state, res.doc()->GetAllocator());
|
||||
}
|
||||
|
||||
res.add_data(response_object);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "api/module.h"
|
||||
#include "api/request.h"
|
||||
|
||||
namespace api::modules {
|
||||
|
||||
class DDR : public Module {
|
||||
public:
|
||||
DDR();
|
||||
|
||||
private:
|
||||
// function definitions
|
||||
void tapeled_get(Request &req, Response &res);
|
||||
};
|
||||
}
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "api/module.h"
|
||||
#include "api/request.h"
|
||||
|
||||
namespace api::modules {
|
||||
|
||||
class DDR : public Module {
|
||||
public:
|
||||
DDR();
|
||||
|
||||
private:
|
||||
// function definitions
|
||||
void tapeled_get(Request &req, Response &res);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,14 +106,16 @@ namespace api::modules {
|
||||
void IIDX::copy_tapeled_data(Response &res, Value &response_object, const tapeledutils::tape_led &mapping) {
|
||||
// Create an array for the light state
|
||||
Value light_state(kArrayType);
|
||||
light_state.Reserve(mapping.data.capacity() * 3, res.doc()->GetAllocator());
|
||||
light_state.Reserve(
|
||||
static_cast<SizeType>(mapping.data.size() * 3),
|
||||
res.doc()->GetAllocator());
|
||||
for (const auto [r, g, b] : mapping.data) {
|
||||
light_state.PushBack(r, res.doc()->GetAllocator());
|
||||
light_state.PushBack(g, res.doc()->GetAllocator());
|
||||
light_state.PushBack(b, res.doc()->GetAllocator());
|
||||
}
|
||||
|
||||
// Can't use StringRef here, turns some strings partially into null bytes for some reason
|
||||
// can't use StringRef here, turns some strings partially into null bytes for some reason
|
||||
Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator());
|
||||
response_object.AddMember(light_name, light_state, res.doc()->GetAllocator());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "keypads.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -91,11 +93,11 @@ namespace api::modules {
|
||||
|
||||
// set
|
||||
eamuse_set_keypad_overrides(keypad, state);
|
||||
Sleep(sleep_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
||||
|
||||
// unset
|
||||
eamuse_set_keypad_overrides(keypad, 0);
|
||||
Sleep(sleep_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
@@ -118,7 +118,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
@@ -196,7 +196,7 @@ namespace api::modules {
|
||||
|
||||
// check if file exists in modules
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
return error(res, "Couldn't find " + dll_path.string());
|
||||
return error(res, fmt::format("Couldn't find {}", dll_path));
|
||||
}
|
||||
|
||||
// get module
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "sdvx.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace std::placeholders;
|
||||
using namespace rapidjson;
|
||||
|
||||
namespace api::modules {
|
||||
|
||||
SDVX::SDVX() : Module("sdvx") {
|
||||
functions["tapeled_get"] = std::bind(&SDVX::tapeled_get, this, _1, _2);
|
||||
|
||||
for (auto &light : games::sdvx::TAPELED_MAPPING) {
|
||||
lights_by_names.emplace(light.lightName, light);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tapeled_get()
|
||||
* tapeled_get(name: str, ...)
|
||||
*/
|
||||
void SDVX::tapeled_get(Request &req, Response &res) {
|
||||
Value response_object(kObjectType);
|
||||
|
||||
// all tape leds
|
||||
if (req.params.Size() == 0) {
|
||||
// iterate through each device and dump its lights data into the response
|
||||
for (const auto &mapping : games::sdvx::TAPELED_MAPPING) {
|
||||
copy_tapeled_data(res, response_object, mapping);
|
||||
}
|
||||
} else {
|
||||
// specified light names
|
||||
for (Value ¶m : req.params.GetArray()) {
|
||||
// check params
|
||||
if (!param.IsString()) {
|
||||
error_type(res, "name", "string");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto name = param.GetString();
|
||||
if (const auto &it = lights_by_names.find(name); it != lights_by_names.end()) {
|
||||
copy_tapeled_data(res, response_object, it->second.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.add_data(response_object);
|
||||
}
|
||||
|
||||
void SDVX::copy_tapeled_data(Response &res, Value &response_object,
|
||||
const tapeledutils::tape_led &mapping)
|
||||
{
|
||||
Value light_state(kArrayType);
|
||||
light_state.Reserve(
|
||||
static_cast<SizeType>(mapping.data.size() * 3),
|
||||
res.doc()->GetAllocator());
|
||||
for (const auto [r, g, b] : mapping.data) {
|
||||
light_state.PushBack(r, res.doc()->GetAllocator());
|
||||
light_state.PushBack(g, res.doc()->GetAllocator());
|
||||
light_state.PushBack(b, res.doc()->GetAllocator());
|
||||
}
|
||||
|
||||
// can't use StringRef here, turns some strings partially into null bytes for some reason
|
||||
Value light_name(mapping.lightName.c_str(), res.doc()->GetAllocator());
|
||||
response_object.AddMember(light_name, light_state, res.doc()->GetAllocator());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "api/module.h"
|
||||
#include "api/request.h"
|
||||
#include "external/robin_hood.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
|
||||
namespace api::modules {
|
||||
|
||||
class SDVX : public Module {
|
||||
public:
|
||||
SDVX();
|
||||
|
||||
private:
|
||||
robin_hood::unordered_map<std::string, std::reference_wrapper<tapeledutils::tape_led>> lights_by_names;
|
||||
|
||||
void tapeled_get(Request &req, Response &res);
|
||||
void copy_tapeled_data(Response &res, rapidjson::Value &response_object,
|
||||
const tapeledutils::tape_led &mapping);
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "touch.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "external/rapidjson/document.h"
|
||||
@@ -8,7 +10,11 @@
|
||||
#include "misc/eamuse.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "touch/touch.h"
|
||||
#include "touch/native/inject.h"
|
||||
#include "touch/native/nativetouchhook.h"
|
||||
#include "touch/native/transform.h"
|
||||
#include "util/utils.h"
|
||||
#include "games/gitadora/gitadora.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
|
||||
using namespace std::placeholders;
|
||||
@@ -17,6 +23,54 @@ using namespace rapidjson;
|
||||
|
||||
namespace api::modules {
|
||||
|
||||
static bool inject_native_touch(
|
||||
const std::vector<TouchPoint> &touch_points, int canvas_w, int canvas_h) {
|
||||
if (touch_points.empty()) {
|
||||
// no active touches remain; release the synthetic contact
|
||||
return nativetouch::inject::inject_synthetic_touch({ 0, 0 }, false);
|
||||
}
|
||||
|
||||
// only a single synthetic contact is supported, so just use the first touch point
|
||||
const auto &touch = touch_points.front();
|
||||
POINT position { touch.x, touch.y };
|
||||
if (canvas_w > 0 && canvas_h > 0) {
|
||||
return nativetouch::inject::inject_synthetic_touch_from_canvas(
|
||||
position,
|
||||
{ canvas_w, canvas_h },
|
||||
true);
|
||||
}
|
||||
return nativetouch::inject::inject_synthetic_touch(position, true);
|
||||
}
|
||||
|
||||
// map API coordinates onto the touch space SDVX reads, which depends on how it is displayed
|
||||
static void sdvx_touch_errata(
|
||||
int &x, int &y, bool use_native, int canvas_w, int canvas_h) {
|
||||
|
||||
// windowed coordinates already match the sub screen window they land on
|
||||
if (GRAPHICS_WINDOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// landscape mode: native injection hands the game these coordinates
|
||||
// unchanged, so apply the rotation the touchscreen gets, while wintouchemu instead
|
||||
// rotates them later through the subscreen overlay
|
||||
if (GRAPHICS_FS_ORIENTATION_SWAP) {
|
||||
if (use_native) {
|
||||
POINT position { x, y };
|
||||
if (nativetouch::transform::sdvx_landscape_rotate(&position, canvas_w, canvas_h)) {
|
||||
x = position.x;
|
||||
y = position.y;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// rotate into the portrait touch space
|
||||
const int x_raw = x;
|
||||
x = canvas_w - y;
|
||||
y = x_raw;
|
||||
}
|
||||
|
||||
Touch::Touch() : Module("touch") {
|
||||
is_sdvx = avs::game::is_model("KFC");
|
||||
|
||||
@@ -26,6 +80,32 @@ namespace api::modules {
|
||||
is_tdj_fhd = false;
|
||||
}
|
||||
|
||||
use_native = nativetouch::is_hooked();
|
||||
|
||||
// resolution the API/companion sends native touch coordinates in (after errata)
|
||||
native_canvas_w = 0;
|
||||
native_canvas_h = 0;
|
||||
if (is_sdvx) {
|
||||
// windowed API coordinates land on the sub screen window as-is; fullscreen
|
||||
// coordinates are rotated into the game's touch space by apply_touch_errata
|
||||
const bool landscape_coordinates =
|
||||
GRAPHICS_WINDOWED || GRAPHICS_FS_ORIENTATION_SWAP;
|
||||
native_canvas_w = landscape_coordinates ? 1920 : 1080;
|
||||
native_canvas_h = landscape_coordinates ? 1080 : 1920;
|
||||
} else if (avs::game::is_model("LDJ")) {
|
||||
// TDJ subscreen; FHD models are upscaled to 1080p by apply_touch_errata
|
||||
native_canvas_w = is_tdj_fhd ? 1920 : 1280;
|
||||
native_canvas_h = is_tdj_fhd ? 1080 : 720;
|
||||
} else if (avs::game::is_model("M39")) {
|
||||
// pop'n music API touch surface
|
||||
native_canvas_w = 1280;
|
||||
native_canvas_h = 800;
|
||||
} else if (games::gitadora::is_arena_model()) {
|
||||
// GITADORA arena SMALL subscreen, either in its own window or in the overlay
|
||||
native_canvas_w = games::gitadora::ARENA_SUBSCREEN_WIDTH;
|
||||
native_canvas_h = games::gitadora::ARENA_SUBSCREEN_HEIGHT;
|
||||
}
|
||||
|
||||
functions["read"] = std::bind(&Touch::read, this, _1, _2);
|
||||
functions["write"] = std::bind(&Touch::write, this, _1, _2);
|
||||
functions["write_reset"] = std::bind(&Touch::write_reset, this, _1, _2);
|
||||
@@ -99,7 +179,11 @@ namespace api::modules {
|
||||
}
|
||||
|
||||
// apply touch points
|
||||
touch_write_points(&touch_points);
|
||||
if (use_native) {
|
||||
write_native(touch_points);
|
||||
} else {
|
||||
touch_write_points(&touch_points);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,23 +207,54 @@ namespace api::modules {
|
||||
}
|
||||
|
||||
// remove all IDs
|
||||
touch_remove_points(&touch_point_ids);
|
||||
if (use_native) {
|
||||
write_reset_native(touch_point_ids);
|
||||
} else {
|
||||
touch_remove_points(&touch_point_ids);
|
||||
}
|
||||
}
|
||||
|
||||
void Touch::write_native(const std::vector<TouchPoint> &touch_points) {
|
||||
if (touch_points.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(native_touch_mutex);
|
||||
const auto touch_id = touch_points.front().id;
|
||||
if (native_touch_id && *native_touch_id != touch_id) {
|
||||
if (!inject_native_touch({}, native_canvas_w, native_canvas_h)) {
|
||||
return;
|
||||
}
|
||||
native_touch_id.reset();
|
||||
}
|
||||
if (inject_native_touch(touch_points, native_canvas_w, native_canvas_h)) {
|
||||
native_touch_id = touch_id;
|
||||
}
|
||||
}
|
||||
|
||||
void Touch::write_reset_native(const std::vector<DWORD> &touch_point_ids) {
|
||||
std::lock_guard<std::mutex> lock(native_touch_mutex);
|
||||
if (!native_touch_id ||
|
||||
std::find(
|
||||
touch_point_ids.begin(),
|
||||
touch_point_ids.end(),
|
||||
*native_touch_id) == touch_point_ids.end()) {
|
||||
return;
|
||||
}
|
||||
if (inject_native_touch({}, native_canvas_w, native_canvas_h)) {
|
||||
native_touch_id.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Touch::apply_touch_errata(int &x, int &y) {
|
||||
int x_raw = x;
|
||||
int y_raw = y;
|
||||
|
||||
if (is_tdj_fhd) {
|
||||
// deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p)
|
||||
// we don't know what screen is being shown on the companion and the API doesn't specify
|
||||
// the target of the touch events so just assume it's the sub screen
|
||||
x = x_raw * 1920 / 1280;
|
||||
y = y_raw * 1080 / 720;
|
||||
x = x * 1920 / 1280;
|
||||
y = y * 1080 / 720;
|
||||
} else if (is_sdvx) {
|
||||
// for exceed gear, they are both 1080p screens, but need to apply transformation
|
||||
x = 1080 - y_raw;
|
||||
y = x_raw;
|
||||
sdvx_touch_errata(x, y, use_native, native_canvas_w, native_canvas_h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "api/module.h"
|
||||
#include "api/request.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
namespace api::modules {
|
||||
|
||||
@@ -13,11 +17,21 @@ namespace api::modules {
|
||||
private:
|
||||
bool is_sdvx;
|
||||
bool is_tdj_fhd;
|
||||
bool use_native;
|
||||
std::mutex native_touch_mutex;
|
||||
std::optional<uint32_t> native_touch_id;
|
||||
|
||||
// resolution the API/companion sends native touch coordinates in (after errata);
|
||||
// used to normalize before mapping into the native injection window
|
||||
int native_canvas_w;
|
||||
int native_canvas_h;
|
||||
|
||||
// function definitions
|
||||
void read(Request &req, Response &res);
|
||||
void write(Request &req, Response &res);
|
||||
void write_reset(Request &req, Response &res);
|
||||
void write_native(const std::vector<TouchPoint> &touch_points);
|
||||
void write_reset_native(const std::vector<DWORD> &touch_point_ids);
|
||||
void apply_touch_errata(int &x, int &y);
|
||||
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ from .coin import *
|
||||
from .control import *
|
||||
from .exceptions import *
|
||||
from .iidx import *
|
||||
from .sdvx import *
|
||||
from .info import *
|
||||
from .keypads import *
|
||||
from .lights import *
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
from .connection import Connection
|
||||
from .request import Request
|
||||
|
||||
|
||||
def sdvx_tapeled_get(con: Connection, *light_names):
|
||||
req = Request("sdvx", "tapeled_get")
|
||||
|
||||
for light_name in light_names:
|
||||
req.add_param(light_name)
|
||||
|
||||
res = con.request(req)
|
||||
return res.get_data()
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "controller.h"
|
||||
#include "serial.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "util/logging.h"
|
||||
@@ -160,7 +162,7 @@ namespace api {
|
||||
|
||||
// slow down on reconnect
|
||||
if (this->running) {
|
||||
Sleep(retry_time);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retry_time));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "util/utils.h"
|
||||
#include "util/rc4.h"
|
||||
#include "util/logging.h"
|
||||
#include "overlay/notifications.h"
|
||||
#include "controller.h"
|
||||
|
||||
using namespace headsocket;
|
||||
@@ -91,12 +92,18 @@ namespace api {
|
||||
|
||||
// log connection
|
||||
log_info("api::websocket", "client connected");
|
||||
overlay::notifications::add(
|
||||
overlay::notifications::Severity::Success,
|
||||
"API websocket client connected");
|
||||
}
|
||||
|
||||
void WebSocketClient::on_disconnect() {
|
||||
|
||||
// log disconnection
|
||||
log_info("api::websocket", "client disconnected");
|
||||
overlay::notifications::add(
|
||||
overlay::notifications::Severity::Info,
|
||||
"API websocket client disconnected");
|
||||
|
||||
// get pointer to server
|
||||
auto srv = reinterpret_cast<WebSocketServer *>(server().get());
|
||||
|
||||
@@ -1114,6 +1114,7 @@ namespace avs {
|
||||
#endif
|
||||
// jubeat
|
||||
{"jubeat.dll", 0x2000000},
|
||||
{"jubeat2019.dll", 0x10000000},
|
||||
|
||||
// MUSECA
|
||||
{"museca.dll", 0xC000000},
|
||||
@@ -1168,7 +1169,7 @@ namespace avs {
|
||||
// Ongaku Paradise
|
||||
{"arkjc9.dll", 0xA00000},
|
||||
|
||||
// KAMUNITY
|
||||
// KAMUNITY
|
||||
{"kamunity.dll", 33554432},
|
||||
};
|
||||
|
||||
@@ -1516,7 +1517,7 @@ namespace avs {
|
||||
" * It's also possible that you have incomplete game data\n"
|
||||
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
|
||||
"\n"
|
||||
, DLL_NAME, MODULE_PATH.string()) };
|
||||
, DLL_NAME, MODULE_PATH) };
|
||||
log_warning("avs-ea3", "{}", info_str);
|
||||
log_fatal("avs-ea3", "Failed to find critical avs DLL on disk (avs2-core.dll OR {})", DLL_NAME);
|
||||
}
|
||||
@@ -1757,9 +1758,9 @@ namespace avs {
|
||||
{
|
||||
deferredlogs::defer_error_messages({
|
||||
"AVS filesystem initialization failure was previously detected during boot!",
|
||||
fmt::format(" ERROR: directory could not be created: {}", src_path.string().c_str()),
|
||||
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH.c_str()),
|
||||
" fix the XML file and try again"
|
||||
fmt::format(" ERROR: directory could not be created: {}", src_path),
|
||||
fmt::format(" if you see a crash, it may have been caused by bad <mounttable> contents in {}", avs::core::CFG_PATH),
|
||||
" fix the XML file and try again"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1780,14 +1781,14 @@ namespace avs {
|
||||
auto created = std::filesystem::create_directories(real_path, err);
|
||||
|
||||
if (created) {
|
||||
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path.string());
|
||||
log_info("avs-core", "created '{}' at '{}'", avs_path, real_path);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
avs_dir_err(real_path);
|
||||
log_warning("avs-core", "failed to create '{}' folder at '{}': {}",
|
||||
avs_path,
|
||||
real_path.string(),
|
||||
real_path,
|
||||
err.message());
|
||||
}
|
||||
}
|
||||
@@ -1921,6 +1922,15 @@ namespace avs {
|
||||
// create nvram and raw directories if possible for mounttable configurations
|
||||
create_avs_config_fs_table(config, config_node);
|
||||
|
||||
#if !SPICE64
|
||||
// sdvx4 bad log config fix
|
||||
if (avs::game::DLL_NAME == "soundvoltex.dll" && // it's too early for avs::game::is_model
|
||||
property_search(config, config_node, "/log/enable_console")) {
|
||||
log_info("avs-core", "applying SDVX4 avs-config.xml fix for <log><enable_console>");
|
||||
property_search_remove_safe(config, config_node, "/log/enable_console");
|
||||
}
|
||||
#endif
|
||||
|
||||
// set log level
|
||||
if (!LOG_LEVEL_CUSTOM.empty()) {
|
||||
property_search_remove_safe(config, config_node, "/log/level");
|
||||
|
||||
@@ -151,8 +151,8 @@ namespace avs {
|
||||
" * It's also possible that you have incomplete game data\n"
|
||||
" * Do NOT copy over random DLLs from another game installation; DLL must match game version\n"
|
||||
"\n"
|
||||
, DLL_NAME, MODULE_PATH.string()) };
|
||||
|
||||
, DLL_NAME, MODULE_PATH) };
|
||||
|
||||
log_warning("avs-ea3", "{}", info_str);
|
||||
log_fatal("avs-ea3", "Failed to find critical ea3 DLL on disk (avs2-ea3.dll OR {})", DLL_NAME);
|
||||
}
|
||||
@@ -217,8 +217,10 @@ namespace avs {
|
||||
ea3_config_name = "prop/eamuse-config.xml";
|
||||
}
|
||||
|
||||
if (avs::core::file_exists(ea3_config_name)) {
|
||||
log_info("avs-ea3", "booting (using {})", ea3_config_name);
|
||||
if (fileutils::file_exists(ea3_config_name)) {
|
||||
log_info("avs-ea3", "found {} on disk", ea3_config_name);
|
||||
} else if (CFG_PATH.size()) {
|
||||
log_fatal("avs-ea3", "user-specified ea3 config file is missing: {}", ea3_config_name);
|
||||
} else {
|
||||
log_warning("avs-ea3", "looked for the following files in order:");
|
||||
log_warning("avs-ea3", " * prop/ea3-config.xml");
|
||||
@@ -388,8 +390,8 @@ namespace avs {
|
||||
|
||||
// fall back to default PCBID if node is not found
|
||||
if (!EA3_PCBID[0] && PCBID_CUSTOM.empty()) {
|
||||
log_warning("avs-ea3", "no PCBID set, falling back to default PCBID value (04040000000000000000)");
|
||||
PCBID_CUSTOM = "04040000000000000000";
|
||||
log_warning("avs-ea3", "no PCBID set, falling back to default PCBID value (01201000000000010101)");
|
||||
PCBID_CUSTOM = "01201000000000010101";
|
||||
}
|
||||
|
||||
// custom PCBID
|
||||
@@ -589,6 +591,7 @@ namespace avs {
|
||||
// for proper reporting of Omnimix and other song packs
|
||||
if (_stricmp(EA3_MODEL, "LDJ") == 0 ||
|
||||
_stricmp(EA3_MODEL, "L44") == 0 ||
|
||||
_stricmp(EA3_MODEL, "T44") == 0 ||
|
||||
_stricmp(EA3_MODEL, "M39") == 0 ||
|
||||
_stricmp(EA3_MODEL, "KFC") == 0)
|
||||
{
|
||||
|
||||
@@ -73,11 +73,10 @@ namespace avs {
|
||||
|
||||
// load game instance
|
||||
const auto dll_path = MODULE_PATH / DLL_NAME;
|
||||
const auto dll_path_s = dll_path.string();
|
||||
log_info("avs-game", "DLL path: {}", dll_path_s.c_str());
|
||||
log_info("avs-game", "DLL path: {}", dll_path);
|
||||
|
||||
// MAX_PATH is 260
|
||||
if (130 <= dll_path_s.length()) {
|
||||
if (const auto dll_path_len = dll_path.wstring().length(); 130 <= dll_path_len) {
|
||||
log_warning(
|
||||
"avs-game",
|
||||
"PATH TOO LONG WARNING\n\n"
|
||||
@@ -93,7 +92,7 @@ namespace avs {
|
||||
"long, often resulting in random crashes. Move the game contents to\n"
|
||||
"a directory with shorter path.\n"
|
||||
"-------------------------------------------------------------------\n\n",
|
||||
dll_path_s, dll_path_s.length());
|
||||
dll_path, dll_path_len);
|
||||
}
|
||||
|
||||
// ddr gamemdx.dll user error
|
||||
@@ -112,7 +111,7 @@ namespace avs {
|
||||
|
||||
// file not found on disk
|
||||
if (!fileutils::file_exists(dll_path)) {
|
||||
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path.string().c_str());
|
||||
log_warning("avs-game", "game DLL could not be found on disk: {}", dll_path);
|
||||
log_warning("avs-game", "double check -exec and -modules parameters; unless you know what you're doing, leave them blank");
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
|
||||
</buttons>
|
||||
<analogs>
|
||||
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="P1 1" devid="Player 1" index="0"/>
|
||||
@@ -117,8 +117,8 @@
|
||||
<keypad_buttons/>
|
||||
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
|
||||
<analogs>
|
||||
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="P1 1" devid="arcin (1p)" index="0"/>
|
||||
@@ -144,34 +144,34 @@
|
||||
<buttons/>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
|
||||
@@ -277,9 +277,9 @@
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights/>
|
||||
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
|
||||
@@ -296,8 +296,8 @@
|
||||
</buttons>
|
||||
<keypad_buttons/>
|
||||
<analogs>
|
||||
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
|
||||
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay_ms="0"/>
|
||||
</analogs>
|
||||
<lights>
|
||||
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
<dpiAware>true</dpiAware>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">Legacy</activeCodePage>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<dependency>
|
||||
@@ -23,7 +24,7 @@
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
File diff suppressed because one or more lines are too long
+73
-32
@@ -68,8 +68,8 @@ DIST_FOLDER="./dist"
|
||||
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
|
||||
DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
|
||||
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
|
||||
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
|
||||
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
|
||||
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm spicetools_sdk_sample_v0_flat_c_32"
|
||||
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux spicetools_sdk_sample_v0_flat_c_64 spicetools_sdk_sample_v0_cpp_64"
|
||||
TARGETS_XP32="spicetools_cfg spicetools_spice"
|
||||
TARGETS_XP64="spicetools_spice64"
|
||||
|
||||
@@ -91,14 +91,20 @@ fi
|
||||
|
||||
# is the XP-compatible toolchain installed?
|
||||
XP_MUST_BUILD=0
|
||||
BUILD_XP=0
|
||||
if [ -f "$TOOLCHAIN_WINXP_32" ] && [ -f "$TOOLCHAIN_WINXP_64" ]; then
|
||||
BUILD_XP=1;
|
||||
# 64-bit WinXP builds are disabled by default; set to 1 to opt in
|
||||
BUILD_XP_64_ENABLE=0
|
||||
BUILD_XP_32=0
|
||||
BUILD_XP_64=0
|
||||
if [ -f "$TOOLCHAIN_WINXP_32" ]; then
|
||||
BUILD_XP_32=1;
|
||||
elif ((XP_MUST_BUILD > 0))
|
||||
then
|
||||
echo "WinXP toolchain not available, aborting"
|
||||
echo "WinXP 32bit toolchain not available, aborting"
|
||||
exit 1
|
||||
fi
|
||||
if ((BUILD_XP_64_ENABLE > 0)) && [ -f "$TOOLCHAIN_WINXP_64" ]; then
|
||||
BUILD_XP_64=1;
|
||||
fi
|
||||
|
||||
# determine number of cores
|
||||
CORES=$(nproc)
|
||||
@@ -113,12 +119,17 @@ echo "Git Branch: $GIT_BRANCH"
|
||||
echo "Git Head: $GIT_HEAD"
|
||||
echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
|
||||
echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
|
||||
if ((BUILD_XP > 0))
|
||||
if ((BUILD_XP_32 > 0))
|
||||
then
|
||||
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
|
||||
else
|
||||
echo "WinXP 32bit toolchain not available, skipping WinXP 32bit builds"
|
||||
fi
|
||||
if ((BUILD_XP_64 > 0))
|
||||
then
|
||||
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
|
||||
else
|
||||
echo "WinXP toolchain not available, skipping WinXP builds"
|
||||
echo "WinXP 64bit builds disabled, skipping WinXP 64bit builds"
|
||||
fi
|
||||
echo "Distribution Name: $DIST_NAME"
|
||||
echo "Build Type: $BUILD_TYPE"
|
||||
@@ -160,9 +171,10 @@ time (
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
|
||||
popd > /dev/null
|
||||
|
||||
if ((BUILD_XP > 0))
|
||||
if ((BUILD_XP_32 > 0))
|
||||
then
|
||||
# 32 bit Windows XP
|
||||
echo ""
|
||||
echo "Building 32bit targets (WinXP toolchain)..."
|
||||
echo "========================="
|
||||
if ((CLEAN_BUILD > 0))
|
||||
@@ -171,9 +183,15 @@ time (
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_WINXP_32}
|
||||
pushd ${BUILDDIR_WINXP_32} > /dev/null
|
||||
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32}
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DSPICE_XP=ON "$OLDPWD" && ninja ${TARGETS_XP32}
|
||||
popd > /dev/null
|
||||
else
|
||||
echo ""
|
||||
echo "Skipping WinXP 32bit builds, toolchain not specified"
|
||||
fi
|
||||
|
||||
if ((BUILD_XP_64 > 0))
|
||||
then
|
||||
# 64 bit Windows XP
|
||||
echo ""
|
||||
echo "Building 64bit targets (WinXP toolchain)..."
|
||||
@@ -184,10 +202,11 @@ time (
|
||||
fi
|
||||
mkdir -p ${BUILDDIR_WINXP_64}
|
||||
pushd ${BUILDDIR_WINXP_64} > /dev/null
|
||||
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
|
||||
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DSPICE_XP=ON "$OLDPWD" && ninja ${TARGETS_XP64}
|
||||
popd > /dev/null
|
||||
else
|
||||
echo "Skipping WinXP builds, toolchain not specified"
|
||||
echo ""
|
||||
echo "Skipping WinXP 64bit builds"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
@@ -195,7 +214,20 @@ time (
|
||||
echo "==========================="
|
||||
)
|
||||
|
||||
if ((BUILD_XP > 0))
|
||||
echo ""
|
||||
echo "Checking Win7 compatibility..."
|
||||
echo "============================="
|
||||
if ! command -v windows_dll_compat_checker &> /dev/null; then
|
||||
echo "WARNING: windows_dll_compat_checker not found, skipping Win7 compatibility check"
|
||||
else
|
||||
windows_dll_compat_checker -s PREMADE/konami_win7_museca_x86_64.ini \
|
||||
${BUILDDIR_64}/spicetools/64/spice64.exe
|
||||
windows_dll_compat_checker -s PREMADE/konami_win7_museca_x86_64_32bit_dlls.ini \
|
||||
${BUILDDIR_32}/spicetools/spicecfg.exe \
|
||||
${BUILDDIR_32}/spicetools/32/spice.exe
|
||||
fi
|
||||
|
||||
if ((BUILD_XP_32 > 0)) || ((BUILD_XP_64 > 0))
|
||||
then
|
||||
echo ""
|
||||
echo "Checking XP compatibility..."
|
||||
@@ -203,11 +235,17 @@ then
|
||||
if ! command -v windows_dll_compat_checker &> /dev/null; then
|
||||
echo "WARNING: windows_dll_compat_checker not found, skipping XP compatibility check"
|
||||
else
|
||||
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
|
||||
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
|
||||
windows_dll_compat_checker -s PREMADE/winxp_x86_64_32bit_dlls.ini \
|
||||
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
|
||||
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe
|
||||
if ((BUILD_XP_64 > 0))
|
||||
then
|
||||
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
|
||||
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
|
||||
fi
|
||||
if ((BUILD_XP_32 > 0))
|
||||
then
|
||||
windows_dll_compat_checker -s PREMADE/konami_winxp_jubeat_i686.ini \
|
||||
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
|
||||
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -267,7 +305,10 @@ rm -rf ${OUTDIR_EXTRAS}
|
||||
mkdir -p ${OUTDIR_EXTRAS}
|
||||
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
|
||||
mkdir -p ${OUTDIR_EXTRAS}/linux
|
||||
if ((BUILD_XP > 0))
|
||||
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/32
|
||||
mkdir -p ${OUTDIR_EXTRAS}/sdk/samples/64
|
||||
mkdir -p ${OUTDIR_EXTRAS}/updater
|
||||
if ((BUILD_XP_32 > 0)) || ((BUILD_XP_64 > 0))
|
||||
then
|
||||
mkdir -p ${OUTDIR_EXTRAS}/winxp
|
||||
fi
|
||||
@@ -279,16 +320,8 @@ then
|
||||
cp ${BUILDDIR_32}/spicetools/spicecfg-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64-pdb.pdb ${OUTDIR} 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
else
|
||||
# release files
|
||||
cp ${BUILDDIR_32}/spicetools/spicecfg.exe ${OUTDIR} 2>/dev/null
|
||||
@@ -296,20 +329,22 @@ else
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice_laa.exe ${OUTDIR_EXTRAS}/largeaddressaware/spice.exe 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/spice_linux.exe ${OUTDIR_EXTRAS}/linux/spice.exe 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kbt.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
#cp ${BUILDDIR_32}/spicetools/32/kld.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64.exe ${OUTDIR} 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/spice64_linux.exe ${OUTDIR_EXTRAS}/linux/spice64.exe 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kbt.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
#cp ${BUILDDIR_64}/spicetools/64/kld.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvEncodeAPI64.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
|
||||
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
|
||||
if ((BUILD_XP > 0))
|
||||
cp ${BUILDDIR_32}/spicetools/32/sdk_sample_v0_flat_c.dll ${OUTDIR_EXTRAS}/sdk/samples/32/v0_flat_c.dll 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/sdk_sample_v0_flat_c.dll ${OUTDIR_EXTRAS}/sdk/samples/64/v0_flat_c.dll 2>/dev/null
|
||||
cp ${BUILDDIR_64}/spicetools/64/sdk_sample_v0_cpp.dll ${OUTDIR_EXTRAS}/sdk/samples/64/v0_cpp.dll 2>/dev/null
|
||||
if ((BUILD_XP_32 > 0))
|
||||
then
|
||||
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
cp ${BUILDDIR_WINXP_32}/spicetools/32/spice.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
fi
|
||||
if ((BUILD_XP_64 > 0))
|
||||
then
|
||||
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
@@ -324,12 +359,18 @@ then
|
||||
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
|
||||
fi
|
||||
|
||||
# sdk headers
|
||||
cp -r ./sdk/include ${OUTDIR_EXTRAS}/sdk
|
||||
|
||||
# copy resources
|
||||
rm -rf ${OUTDIR_EXTRAS}/api
|
||||
mkdir ${OUTDIR_EXTRAS}/api
|
||||
find ./api/resources/python -name "__pycache__" -exec rm -rf {} +
|
||||
cp -r ./api/resources/* ${OUTDIR_EXTRAS}/api
|
||||
|
||||
# generate the standalone updater scripts from the shared template and drop them
|
||||
bash ./build_updaters.sh ${OUTDIR_EXTRAS}/updater
|
||||
|
||||
# build distribution archive
|
||||
if ((DIST_ENABLE > 0))
|
||||
then
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
docker build --pull external/docker -t spicetools/deps
|
||||
docker build --build-context gitroot=%cd%/../../.git . -t spicetools/spice
|
||||
docker run --rm -it -v %cd%/dist:/src/src/spice2x/dist -v %cd%/bin:/src/src/spice2x/bin -v %cd%/.ccache:/src/src/spice2x/.ccache spicetools/spice %*
|
||||
docker run --rm -i -v %cd%/dist:/src/src/spice2x/dist -v %cd%/bin:/src/src/spice2x/bin -v %cd%/.ccache:/src/src/spice2x/.ccache spicetools/spice %*
|
||||
@REM to generate PDBs, set DEBUG to 1 in build_all.sh, place cv2pdb in external\cv2pdb, and run below
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spicecfg.exe bin\spice2x\spicecfg-pdb.exe bin\spice2x\spicecfg-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice.exe bin\spice2x\spice-pdb.exe bin\spice2x\spice-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice64.exe bin\spice2x\spice64-pdb.exe bin\spice2x\spice64-pdb.pdb
|
||||
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice64.exe bin\spice2x\spice64-pdb.exe bin\spice2x\spice64-pdb.pdb
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# builds the standalone updater scripts from the shared PowerShell logic in
|
||||
# update_spice.ps1. each output is a self-contained polyglot .bat: a small batch
|
||||
# header (with the release channel baked in) followed by the shared PowerShell
|
||||
# body after the marker line.
|
||||
#
|
||||
# usage: build_updaters.sh OUT_DIR
|
||||
# OUT_DIR directory to write the generated .bat files into
|
||||
|
||||
set -eu
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "usage: build_updaters.sh OUT_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PS_BODY="${SRC_DIR}/update_spice.ps1"
|
||||
OUT_DIR="$1"
|
||||
|
||||
[ -f "$PS_BODY" ] || { echo "error: $PS_BODY not found" >&2; exit 1; }
|
||||
|
||||
MARKER='#___PS___'
|
||||
|
||||
gen() {
|
||||
channel="$1"; label="$2"; out="$3"
|
||||
{
|
||||
cat <<HEADER
|
||||
@echo off
|
||||
setlocal
|
||||
REM ============================================================================
|
||||
REM spice2x updater - updates spice.exe, spice64.exe and spicecfg.exe in
|
||||
REM this folder to the latest spice2x release from GitHub.
|
||||
REM
|
||||
REM release channel: ${label}
|
||||
REM make sure spice/spicecfg are NOT running before updating.
|
||||
REM ============================================================================
|
||||
|
||||
REM expose the script folder + channel to the embedded PowerShell, then run the
|
||||
REM PowerShell part: re-read this file and execute everything after the marker.
|
||||
set "SPICE_DIR=%~dp0"
|
||||
set "SPICE_CHANNEL=${channel}"
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "\$m='#___'+'PS'+'___'; \$code=((Get-Content -LiteralPath '%~f0' -Raw) -split \$m)[-1]; Invoke-Expression \$code"
|
||||
set "RC=%ERRORLEVEL%"
|
||||
endlocal & exit /b %RC%
|
||||
|
||||
${MARKER}
|
||||
HEADER
|
||||
cat "$PS_BODY"
|
||||
} | sed -e 's/\r$//' -e 's/$/\r/' > "${OUT_DIR}/${out}"
|
||||
echo " generated ${OUT_DIR}/${out}"
|
||||
}
|
||||
|
||||
echo "Generating updater scripts from $(basename "$PS_BODY")..."
|
||||
gen "" "stable (pre-releases excluded)" "update_spice.bat"
|
||||
gen "beta" "beta (newest, pre-releases included)" "update_spice_beta.bat"
|
||||
+102
-15
@@ -207,22 +207,38 @@ float Analog::applyMultiplier(float value) {
|
||||
}
|
||||
|
||||
float Analog::normalizeAnalogValue(float value) {
|
||||
// effectively the same as fmodf(value, 1.f)
|
||||
// for small values, this is MUCH faster than fmodf.
|
||||
float new_value = value;
|
||||
while (new_value > 1.f) {
|
||||
new_value -= 1.f;
|
||||
if (getType() == GameAPI::Analogs::AnalogType::Circular) {
|
||||
// effectively the same as fmodf(value, 1.f)
|
||||
// for small values, this is MUCH faster than fmodf.
|
||||
float new_value = value;
|
||||
while (new_value > 1.f) {
|
||||
new_value -= 1.f;
|
||||
}
|
||||
while (new_value < 0.f) {
|
||||
new_value += 1.f;
|
||||
}
|
||||
return new_value;
|
||||
|
||||
} else {
|
||||
// clamp to [0, 1] range
|
||||
return std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
while (new_value < 0.f) {
|
||||
new_value += 1.f;
|
||||
}
|
||||
return new_value;
|
||||
}
|
||||
|
||||
float Analog::applyDeadzone(float raw_value) {
|
||||
float value = raw_value;
|
||||
const auto deadzone = this->getDeadzone();
|
||||
if (deadzone > 0) {
|
||||
auto deadzone = this->getDeadzone();
|
||||
|
||||
// in the past, positive deadzone applied in the center, negative deadzone applied to 0
|
||||
// after each analog value received a type (circular/linear) this has been simpliifed to
|
||||
// positive values only since we can figure out where the rest value is
|
||||
// for back compat, treat negative value as positive
|
||||
if (deadzone < 0.f) {
|
||||
deadzone = -deadzone;
|
||||
}
|
||||
|
||||
// relative mode assumes that user is using a stick, so center is neutral regardless of analog type
|
||||
if (getType() != GameAPI::Analogs::AnalogType::LinearPositive || isRelativeMode()) {
|
||||
|
||||
// calculate values
|
||||
const auto delta = value - 0.5f;
|
||||
@@ -255,7 +271,7 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (deadzone < 0) {
|
||||
} else {
|
||||
|
||||
// invert for mirror
|
||||
if (this->getDeadzoneMirror()) {
|
||||
@@ -263,8 +279,8 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
|
||||
// deadzone from minimum value
|
||||
if (deadzone > -1 && value > -deadzone) {
|
||||
value = std::min(1.f, (value + deadzone) / (1.f + deadzone));
|
||||
if (deadzone < 1.f && deadzone < value) {
|
||||
value = std::max(0.f, (value - deadzone) / (1.f - deadzone));
|
||||
} else {
|
||||
value = 0.f;
|
||||
}
|
||||
@@ -275,4 +291,75 @@ float Analog::applyDeadzone(float raw_value) {
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
float Analog::getRelativeModeValue(float raw_value) {
|
||||
const auto now = get_performance_seconds();
|
||||
auto delta_time = now - this->rel_mode_last_read_time_s;
|
||||
|
||||
if (this->rel_mode_last_read_time_s != 0.f) {
|
||||
// some heuristics to prevent huge jumps:
|
||||
// * if we went for more than 250ms without polls, discard it (e.g., during loading screens)
|
||||
// * cap the delta at 100ms to prevent huge jumps in case of very infrequent polling
|
||||
if (delta_time < 0.f || 0.25f < delta_time) {
|
||||
delta_time = 0.f;
|
||||
} else if (delta_time > 0.1f) {
|
||||
delta_time = 0.1f;
|
||||
}
|
||||
|
||||
// scale [0, 1] to [-1, 1] to simplify calculations
|
||||
const auto delta_raw_value = (raw_value - 0.5f) * 2.f;
|
||||
|
||||
// target is one revolution per second at max speed at 1.0 sensitivity
|
||||
auto adjusted_delta_value = delta_raw_value * delta_time;
|
||||
|
||||
// multiplier / divisor
|
||||
if (this->getMultiplier() > 1) {
|
||||
adjusted_delta_value *= this->getMultiplier();
|
||||
} else if (this->getMultiplier() < -1) {
|
||||
adjusted_delta_value /= -this->getMultiplier();
|
||||
}
|
||||
|
||||
// sensitivity
|
||||
if (this->isSensitivitySet()) {
|
||||
adjusted_delta_value *= this->getSensitivity();
|
||||
}
|
||||
|
||||
// calculate the new absolute value
|
||||
this->rel_mode_absolute_value += adjusted_delta_value;
|
||||
}
|
||||
|
||||
// update for next poll
|
||||
this->rel_mode_last_read_time_s = now;
|
||||
this->rel_mode_absolute_value = normalizeAnalogValue(this->rel_mode_absolute_value);
|
||||
return this->rel_mode_absolute_value;
|
||||
}
|
||||
|
||||
float Analog::getDelayedValue(float raw_value) {
|
||||
const double delay_ms = static_cast<double>(this->getDelayMs());
|
||||
if (delay_ms == 0.0) {
|
||||
return raw_value;
|
||||
}
|
||||
|
||||
// always push a new value
|
||||
const auto now = get_performance_milliseconds();
|
||||
this->delayed_inputs.emplace(now, raw_value);
|
||||
|
||||
// drain the queue down to reasonable length to prevent unconstrained growth
|
||||
// this would accommodate 1 second at ~1000Hz which is overkill
|
||||
// (UI only allows for 250ms of delay)
|
||||
while (this->delayed_inputs.size() > 1024) {
|
||||
this->delayed_inputs.pop();
|
||||
}
|
||||
|
||||
// pop until we find the oldest value still inside the delay window
|
||||
while (this->delayed_inputs.size() > 1) {
|
||||
const auto delta_t = now - this->delayed_inputs.front().time_in_ms;
|
||||
if (delta_t <= delay_ms) {
|
||||
break;
|
||||
}
|
||||
this->delayed_inputs.pop();
|
||||
}
|
||||
|
||||
return this->delayed_inputs.front().value;
|
||||
}
|
||||
|
||||
+46
-18
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
@@ -13,12 +14,32 @@ namespace rawinput {
|
||||
class RawInputManager;
|
||||
}
|
||||
|
||||
namespace GameAPI::Analogs {
|
||||
enum class AnalogType {
|
||||
// default; values wrap around (below 0 turns into 1, over 1 is 0)
|
||||
// knobs, turntables
|
||||
Circular = 0,
|
||||
|
||||
// typical joystick that rests at the center and caps at [0, 1]
|
||||
LinearCentered = 1,
|
||||
|
||||
// one-directional value (sliders and instruments like piano/drum velocity)
|
||||
// starts at 0 and goes up to 1
|
||||
LinearPositive = 2,
|
||||
};
|
||||
}
|
||||
|
||||
struct AnalogMovingAverage {
|
||||
double time_in_ms;
|
||||
float sine;
|
||||
float cosine;
|
||||
};
|
||||
|
||||
struct AnalogDelayEntry {
|
||||
double time_in_ms;
|
||||
float value;
|
||||
};
|
||||
|
||||
class Analog {
|
||||
private:
|
||||
std::string name;
|
||||
@@ -31,10 +52,11 @@ private:
|
||||
float last_state = 0.5f;
|
||||
bool sensitivity_set = false;
|
||||
bool deadzone_set = false;
|
||||
GameAPI::Analogs::AnalogType type = GameAPI::Analogs::AnalogType::Circular;
|
||||
|
||||
// smoothing function
|
||||
bool smoothing = false;
|
||||
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history;
|
||||
std::array<AnalogMovingAverage, ANALOG_HISTORY_CNT> vector_history = {};
|
||||
int vector_history_index = 0;
|
||||
float smoothed_last_state = 0.f;
|
||||
|
||||
@@ -48,12 +70,13 @@ private:
|
||||
unsigned short divisor_region = 0;
|
||||
|
||||
// relative input mode
|
||||
float absolute_value_for_rel_mode = 0.5f;
|
||||
float rel_mode_absolute_value = 0.5f;
|
||||
float rel_mode_last_read_time_s = 0.f;
|
||||
bool relative_mode = false;
|
||||
|
||||
// circular buffer (delayed input)
|
||||
int delay_buffer_depth = 0;
|
||||
std::queue<float> delay_buffer;
|
||||
// delay
|
||||
uint32_t delay_ms = 0;
|
||||
std::queue<AnalogDelayEntry> delayed_inputs;
|
||||
|
||||
float calculateAngularDifference(float old_rads, float new_rads);
|
||||
float normalizeAngle(float rads);
|
||||
@@ -66,7 +89,8 @@ public:
|
||||
float override_state = 0.5f;
|
||||
|
||||
explicit Analog(std::string name) : name(std::move(name)) {
|
||||
vector_history.fill({0.0, 0.f, 0.f});
|
||||
};
|
||||
explicit Analog(std::string name, GameAPI::Analogs::AnalogType type) : name(std::move(name)), type(type) {
|
||||
};
|
||||
|
||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||
@@ -74,6 +98,8 @@ public:
|
||||
float applyAngularSensitivity(float raw_rads);
|
||||
float applyMultiplier(float raw_value);
|
||||
float applyDeadzone(float raw_value);
|
||||
float getRelativeModeValue(float raw_value);
|
||||
float getDelayedValue(float raw_value);
|
||||
|
||||
inline bool isSet() {
|
||||
if (this->override_enabled) {
|
||||
@@ -90,8 +116,8 @@ public:
|
||||
deadzone_mirror = false;
|
||||
setMultiplier(1);
|
||||
setRelativeMode(false);
|
||||
setDelayBufferDepth(0);
|
||||
setLastState(0.5f);
|
||||
setDelayMs(0);
|
||||
}
|
||||
|
||||
inline void clearBindings() {
|
||||
@@ -193,25 +219,27 @@ public:
|
||||
}
|
||||
|
||||
inline void setRelativeMode(bool relative_mode) {
|
||||
if (relative_mode) {
|
||||
this->smoothing = false;
|
||||
}
|
||||
this->relative_mode = relative_mode;
|
||||
this->absolute_value_for_rel_mode = 0.5f;
|
||||
this->rel_mode_absolute_value = 0.5f;
|
||||
this->rel_mode_last_read_time_s = 0.f;
|
||||
}
|
||||
|
||||
inline float getAbsoluteValue(float relative_delta) {
|
||||
this->absolute_value_for_rel_mode =
|
||||
normalizeAnalogValue(this->absolute_value_for_rel_mode + relative_delta);
|
||||
return this->absolute_value_for_rel_mode;
|
||||
inline GameAPI::Analogs::AnalogType getType() const {
|
||||
return this->type;
|
||||
}
|
||||
|
||||
inline int getDelayBufferDepth() const {
|
||||
return this->delay_buffer_depth;
|
||||
inline void setType(GameAPI::Analogs::AnalogType type) {
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
inline void setDelayBufferDepth(int depth) {
|
||||
this->delay_buffer_depth = depth;
|
||||
inline uint32_t getDelayMs() const {
|
||||
return this->delay_ms;
|
||||
}
|
||||
|
||||
inline std::queue<float> &getDelayBuffer() {
|
||||
return this->delay_buffer;
|
||||
inline void setDelayMs(uint32_t delay_ms) {
|
||||
this->delay_ms = delay_ms;
|
||||
}
|
||||
};
|
||||
|
||||
+193
-69
@@ -3,7 +3,9 @@
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
#include "games/io.h"
|
||||
#include "launcher/superexit.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "rawinput/piuio.h"
|
||||
#include "util/time.h"
|
||||
@@ -62,7 +64,43 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
|
||||
return sorted;
|
||||
}
|
||||
|
||||
GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *manager, Button &_button, bool check_alts) {
|
||||
namespace GameAPI::Buttons {
|
||||
static State get_button_state(
|
||||
rawinput::RawInputManager *manager,
|
||||
Button &button,
|
||||
bool check_alts,
|
||||
bool check_modifiers);
|
||||
|
||||
static bool modifiers_pressed(rawinput::RawInputManager *manager, Button &button);
|
||||
}
|
||||
|
||||
bool GameAPI::Buttons::modifiers_pressed(rawinput::RawInputManager *manager, Button &button) {
|
||||
const auto modifier_mask = button.getModifierMask();
|
||||
if (modifier_mask == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto *modifier_buttons = games::get_buttons_modifiers(eamuse_get_game());
|
||||
if (!modifier_buttons) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t index = 0; index < games::ModifierButtons::Size; index++) {
|
||||
if ((modifier_mask & (UINT8_C(1) << index)) != 0 &&
|
||||
(index >= modifier_buttons->size() ||
|
||||
get_button_state(manager, modifier_buttons->at(index), true, false) !=
|
||||
GameAPI::Buttons::BUTTON_PRESSED)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
GameAPI::Buttons::State GameAPI::Buttons::get_button_state(
|
||||
rawinput::RawInputManager *manager,
|
||||
Button &_button,
|
||||
bool check_alts,
|
||||
bool check_modifiers) {
|
||||
|
||||
// check override
|
||||
if (_button.override_enabled) {
|
||||
@@ -76,6 +114,23 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
std::optional<bool> window_has_focus;
|
||||
while (true) {
|
||||
|
||||
// skip bindings whose required modifiers are not held
|
||||
//
|
||||
// note that modifiers cannot process MIDI as the logic is written
|
||||
// below, since MIDI buttons are event-based (on event, off event, etc)
|
||||
// and cannot be correctly handled by a simple early return
|
||||
// there is no explicit check for MIDI here, but the UI should have
|
||||
// prevented it
|
||||
if (check_modifiers && !modifiers_pressed(manager, *current_button)) {
|
||||
button_count++;
|
||||
if (!alternatives || alternatives->empty() ||
|
||||
button_count - 1 >= alternatives->size()) {
|
||||
return BUTTON_NOT_PRESSED;
|
||||
}
|
||||
current_button = &alternatives->at(button_count - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// naive behavior
|
||||
if (current_button->isNaive()) {
|
||||
GameAPI::Buttons::State state;
|
||||
@@ -199,9 +254,19 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
if (vKey < value_states->size()) {
|
||||
auto value = value_states->at(vKey);
|
||||
if (current_button->getAnalogType() == BAT_POSITIVE) {
|
||||
state = value > 0.6f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
float threshold = 0.6f;
|
||||
if (current_button->getBatThreshold() > 0) {
|
||||
threshold = std::clamp(current_button->getBatThreshold() / 100.f, 0.01f, 0.99f);
|
||||
}
|
||||
state = value > threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
|
||||
} else if (current_button->getAnalogType() == BAT_NEGATIVE) {
|
||||
state = value < 0.4f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
float threshold = 0.4f;
|
||||
if (current_button->getBatThreshold() > 0) {
|
||||
threshold = std::clamp((100 - current_button->getBatThreshold()) / 100.f, 0.01f, 0.99f);
|
||||
}
|
||||
state = value < threshold ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
|
||||
} else {
|
||||
state = value > 0.01f ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
}
|
||||
@@ -401,6 +466,12 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
|
||||
break;
|
||||
}
|
||||
case rawinput::DESTROYED:
|
||||
// device was unplugged. release the button instead of leaving
|
||||
// state at getLastState(), which would latch a held button on
|
||||
// forever (e.g. a MIDI note held while the device is removed)
|
||||
state = BUTTON_NOT_PRESSED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -453,6 +524,10 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
|
||||
}
|
||||
}
|
||||
|
||||
Buttons::State Buttons::getState(rawinput::RawInputManager *manager, Button &button, bool check_alts) {
|
||||
return get_button_state(manager, button, check_alts, true);
|
||||
}
|
||||
|
||||
Buttons::State Buttons::getState(std::unique_ptr<rawinput::RawInputManager> &manager, Button &button, bool check_alts) {
|
||||
if (manager) {
|
||||
return getState(manager.get(), button, check_alts);
|
||||
@@ -468,17 +543,13 @@ static float getVelocityHelper(rawinput::RawInputManager *manager, Button &butto
|
||||
return button.override_velocity;
|
||||
}
|
||||
|
||||
// naive behavior
|
||||
if (button.isNaive()) {
|
||||
if (button.getInvert()) {
|
||||
return (GetAsyncKeyState(button.getVKey()) & 0x8000) ? 0.f : 1.f;
|
||||
} else {
|
||||
return (GetAsyncKeyState(button.getVKey()) & 0x8000) ? 1.f : 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
// get button state
|
||||
Buttons::State button_state = Buttons::getState(manager, button, false);
|
||||
const auto button_state = Buttons::getState(manager, button, false);
|
||||
|
||||
// naive bindings report their digital state as full or zero velocity
|
||||
if (button.isNaive()) {
|
||||
return button_state == Buttons::BUTTON_PRESSED ? 1.f : 0.f;
|
||||
}
|
||||
|
||||
// check if button isn't being pressed
|
||||
if (button_state != Buttons::BUTTON_PRESSED) {
|
||||
@@ -669,81 +740,98 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::D
|
||||
}
|
||||
|
||||
// deadzone
|
||||
if (analog.isDeadzoneSet()) {
|
||||
// do not apply deadzone to circular analogs since it doesn't make sense (except in relative mode)
|
||||
if (analog.isDeadzoneSet() &&
|
||||
(analog.getType() != AnalogType::Circular || analog.isRelativeMode())) {
|
||||
value = analog.applyDeadzone(value);
|
||||
}
|
||||
|
||||
if (analog.isRelativeMode()) {
|
||||
float relative_delta = value - 0.5f;
|
||||
// built-in scaling to make values reasonable
|
||||
relative_delta /= 80.f;
|
||||
if (analog.getType() == AnalogType::Circular) {
|
||||
|
||||
// integer multiplier/divisor
|
||||
const auto mult = analog.getMultiplier();
|
||||
if (mult < -1) {
|
||||
relative_delta /= -mult;
|
||||
} else if (1 < mult) {
|
||||
relative_delta *= mult;
|
||||
}
|
||||
if (analog.isRelativeMode()) {
|
||||
value = analog.getRelativeModeValue(value);
|
||||
|
||||
// sensitivity (ranges from 0.0 to 4.0)
|
||||
if (analog.isSensitivitySet()) {
|
||||
relative_delta *= analog.getSensitivity();
|
||||
}
|
||||
} else {
|
||||
// integer multiplier
|
||||
value = analog.applyMultiplier(value);
|
||||
|
||||
// translate relative movement to absolute value
|
||||
value = analog.getAbsoluteValue(relative_delta);
|
||||
// smoothing/sensitivity
|
||||
if (analog.getSmoothing() || analog.isSensitivitySet()) {
|
||||
float rads = value * (float) M_TAU;
|
||||
|
||||
} else {
|
||||
// integer multiplier
|
||||
value = analog.applyMultiplier(value);
|
||||
// smoothing
|
||||
if (analog.getSmoothing()) {
|
||||
|
||||
// smoothing/sensitivity
|
||||
if (analog.getSmoothing() || analog.isSensitivitySet()) {
|
||||
float rads = value * (float) M_TAU;
|
||||
// preserve direction
|
||||
if (rads >= M_TAU) {
|
||||
rads -= 0.0001f;
|
||||
}
|
||||
|
||||
// smoothing
|
||||
if (analog.getSmoothing()) {
|
||||
|
||||
// preserve direction
|
||||
if (rads >= M_TAU) {
|
||||
rads -= 0.0001f;
|
||||
// calculate angle
|
||||
rads = analog.getSmoothedValue(rads);
|
||||
}
|
||||
|
||||
// calculate angle
|
||||
rads = analog.getSmoothedValue(rads);
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
rads = analog.applyAngularSensitivity(rads);
|
||||
}
|
||||
|
||||
// apply to value
|
||||
value = rads * (float) M_1_TAU;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
// adjust curve
|
||||
// values < 1.f : less sensitive around neutral
|
||||
// values > 1.f : more sensitive around neutral
|
||||
float curve = analog.getSensitivity();
|
||||
if (curve <= 0.f) {
|
||||
curve = 0.01f;
|
||||
}
|
||||
curve = 1.f / curve;
|
||||
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
// convert 0.0..1.0 to -1.0..+1.0
|
||||
float signed_raw = (value - 0.5f) * 2.0f;
|
||||
// apply curve
|
||||
float sign = signed_raw < 0.0f ? -1.0f : 1.0f;
|
||||
float magnitude = fabsf(signed_raw);
|
||||
float curved = sign * powf(magnitude, curve);
|
||||
// convert back to 0.0..1.0
|
||||
value = curved * 0.5f + 0.5f;
|
||||
} else {
|
||||
value = powf(value, curve);
|
||||
}
|
||||
|
||||
// sensitivity
|
||||
if (analog.isSensitivitySet()) {
|
||||
rads = analog.applyAngularSensitivity(rads);
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
|
||||
// multiplier / divisor
|
||||
if (analog.getMultiplier() < -1) {
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
value = (value - 0.5f) / (-analog.getMultiplier()) + 0.5f;
|
||||
} else {
|
||||
value /= -analog.getMultiplier();
|
||||
}
|
||||
|
||||
// apply to value
|
||||
value = rads * (float) M_1_TAU;
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
|
||||
} else if (analog.getMultiplier() > 1) {
|
||||
if (analog.getType() == AnalogType::LinearCentered) {
|
||||
value = (value - 0.5f) * analog.getMultiplier() + 0.5f;
|
||||
} else {
|
||||
value *= analog.getMultiplier();
|
||||
}
|
||||
|
||||
value = std::clamp(value, 0.f, 1.f);
|
||||
}
|
||||
}
|
||||
|
||||
// delay
|
||||
if (0 < analog.getDelayBufferDepth()) {
|
||||
auto& queue = analog.getDelayBuffer();
|
||||
|
||||
// ensure the queue isn't too long; drop old values
|
||||
while (analog.getDelayBufferDepth() <= (int)queue.size()) {
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
// always push new value
|
||||
queue.push(value);
|
||||
|
||||
// get a new value to return
|
||||
if ((int)queue.size() < analog.getDelayBufferDepth()) {
|
||||
// not enough in the queue, stall for now, shouldn't happen often
|
||||
value = analog.getLastState();
|
||||
} else {
|
||||
value = queue.front();
|
||||
queue.pop();
|
||||
}
|
||||
if (analog.getDelayMs() > 0) {
|
||||
value = analog.getDelayedValue(value);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -829,6 +917,42 @@ std::vector<Analog> GameAPI::Analogs::sortAnalogs(
|
||||
return sorted;
|
||||
}
|
||||
|
||||
static std::vector<Analog> sortAnalogsWithTypeInternal(
|
||||
std::vector<Analog> &analogs,
|
||||
const std::initializer_list<GameAPI::Analogs::AnalogWithType> list) {
|
||||
|
||||
std::vector<Analog> sorted;
|
||||
|
||||
bool analog_found;
|
||||
for (auto &a : list) {
|
||||
analog_found = false;
|
||||
|
||||
for (auto &analog : analogs) {
|
||||
if (a.name == analog.getName()) {
|
||||
analog_found = true;
|
||||
analog.setType(a.type);
|
||||
sorted.push_back(analog);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!analog_found) {
|
||||
sorted.emplace_back(a.name, a.type);
|
||||
}
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
void GameAPI::Analogs::sortAnalogsWithType(
|
||||
std::vector<Analog> *analogs,
|
||||
const std::initializer_list<AnalogWithType> list) {
|
||||
|
||||
if (analogs) {
|
||||
*analogs = sortAnalogsWithTypeInternal(*analogs, list);
|
||||
}
|
||||
}
|
||||
|
||||
float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &analog) {
|
||||
|
||||
// check override
|
||||
|
||||
@@ -85,14 +85,15 @@ namespace GameAPI {
|
||||
const std::vector<Analog> &analogs,
|
||||
const std::vector<std::string> &analog_names);
|
||||
|
||||
template<typename T>
|
||||
void sortAnalogs(std::vector<Analog> *analogs, T t) {
|
||||
const std::vector<std::string> analog_names { t };
|
||||
struct AnalogWithType {
|
||||
const std::string name;
|
||||
const AnalogType type;
|
||||
|
||||
if (analogs) {
|
||||
*analogs = GameAPI::Analogs::sortAnalogs(*analogs, analog_names);
|
||||
}
|
||||
}
|
||||
AnalogWithType(std::string name, AnalogType type) :
|
||||
name(std::move(name)), type(type) {}
|
||||
};
|
||||
|
||||
void sortAnalogsWithType(std::vector<Analog> *analogs, const std::initializer_list<AnalogWithType> list);
|
||||
|
||||
template<typename T, typename... Rest>
|
||||
void sortAnalogs(std::vector<Analog> *analogs, T t, Rest... rest) {
|
||||
|
||||
@@ -299,14 +299,37 @@ std::string Button::getMidiNoteString() {
|
||||
return fmt::format("{}{}", note_names[index % 12], ((index / 12) - 1));
|
||||
}
|
||||
|
||||
std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
|
||||
std::string Button::getDisplayString(rawinput::RawInputManager *manager) {
|
||||
auto binding_display = this->getBindingDisplayString(manager);
|
||||
auto modifiers = this->getModifierMask();
|
||||
if (binding_display.empty() || modifiers == 0) {
|
||||
return binding_display;
|
||||
}
|
||||
|
||||
std::string modifier_display;
|
||||
for (unsigned int index = 1; modifiers != 0; index++, modifiers >>= 1) {
|
||||
if ((modifiers & UINT8_C(1)) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!modifier_display.empty()) {
|
||||
modifier_display += "+";
|
||||
}
|
||||
modifier_display += fmt::format("Mod{}", index);
|
||||
}
|
||||
return modifier_display + "+" + binding_display;
|
||||
}
|
||||
|
||||
std::string Button::getBindingDisplayString(rawinput::RawInputManager *manager) {
|
||||
|
||||
// get VKey string
|
||||
auto vKey = (uint16_t) this->getVKey();
|
||||
std::string vKeyString = fmt::format("{:#x}", vKey);
|
||||
|
||||
// device must be existing
|
||||
if (this->device_identifier.empty() && vKey == INVALID_VKEY) {
|
||||
if (this->isNaive() && vKey == INVALID_VKEY) {
|
||||
if (this->getInvert()) {
|
||||
return "(always on)";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
std::string name;
|
||||
std::string device_identifier = "";
|
||||
unsigned short vKey = INVALID_VKEY;
|
||||
uint8_t modifier_mask = 0;
|
||||
|
||||
// default bindings are always naive
|
||||
unsigned short vKey_default = INVALID_VKEY;
|
||||
@@ -56,6 +57,7 @@ private:
|
||||
ButtonAnalogType analog_type = BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0; // for positive/negative only, value in percentage, zero is default
|
||||
bool invert = false;
|
||||
bool is_temporary = false;
|
||||
|
||||
@@ -64,6 +66,7 @@ private:
|
||||
unsigned short velocity_threshold = 0;
|
||||
|
||||
std::string getMidiNoteString();
|
||||
std::string getBindingDisplayString(rawinput::RawInputManager *manager);
|
||||
|
||||
public:
|
||||
std::string getVKeyString();
|
||||
@@ -109,6 +112,8 @@ public:
|
||||
alternatives.clear();
|
||||
device_identifier = "";
|
||||
analog_type = BAT_NONE;
|
||||
bat_threshold = 0;
|
||||
modifier_mask = 0;
|
||||
}
|
||||
|
||||
std::string getDisplayString(rawinput::RawInputManager* manager);
|
||||
@@ -121,6 +126,14 @@ public:
|
||||
return this->name;
|
||||
}
|
||||
|
||||
inline uint8_t getModifierMask() const {
|
||||
return this->modifier_mask;
|
||||
}
|
||||
|
||||
inline void setModifierMask(uint8_t new_modifier_mask) {
|
||||
this->modifier_mask = new_modifier_mask & UINT8_C(0x0F);
|
||||
}
|
||||
|
||||
inline const std::string &getDeviceIdentifier() const {
|
||||
return this->device_identifier;
|
||||
}
|
||||
@@ -169,6 +182,14 @@ public:
|
||||
this->debounce_down = debounce_time_down;
|
||||
}
|
||||
|
||||
inline void setBatThreshold(int bat_threshold) {
|
||||
this->bat_threshold = bat_threshold;
|
||||
}
|
||||
|
||||
inline int getBatThreshold() const {
|
||||
return this->bat_threshold;
|
||||
}
|
||||
|
||||
inline bool getInvert() const {
|
||||
return this->invert;
|
||||
}
|
||||
|
||||
+71
-25
@@ -28,7 +28,7 @@ Config::Config() {
|
||||
this->status = false;
|
||||
if (CONFIG_PATH_OVERRIDE.length() > 0) {
|
||||
this->configLocation = CONFIG_PATH_OVERRIDE;
|
||||
log_info("cfg", "using custom config file: {}", this->configLocation.string());
|
||||
log_info("cfg", "using custom config file: {}", this->configLocation);
|
||||
} else {
|
||||
this->configLocation = std::filesystem::path(_wgetenv(L"APPDATA")) / L"spicetools.xml";
|
||||
// avoids logging the expanded appdata path as it contains user name
|
||||
@@ -57,7 +57,7 @@ Config::Config() {
|
||||
this->firstFillConfigFile();
|
||||
break;
|
||||
case tinyxml2::XMLError::XML_ERROR_FILE_COULD_NOT_BE_OPENED:
|
||||
log_fatal("cfg", "could not open config file: {}", this->configLocation.string());
|
||||
log_fatal("cfg", "could not open config file: {}", this->configLocation);
|
||||
break;
|
||||
case tinyxml2::XMLError::XML_ERROR_FILE_NOT_FOUND:
|
||||
this->createConfigFile();
|
||||
@@ -72,7 +72,7 @@ Config::Config() {
|
||||
case tinyxml2::XMLError::XML_ERROR_PARSING_UNKNOWN:
|
||||
case tinyxml2::XMLError::XML_ERROR_MISMATCHED_ELEMENT:
|
||||
case tinyxml2::XMLError::XML_ERROR_PARSING:
|
||||
log_warning("cfg", "Couldn't read config file: {}", this->configLocation.string());
|
||||
log_warning("cfg", "Couldn't read config file: {}", this->configLocation);
|
||||
this->createConfigFile();
|
||||
this->firstFillConfigFile();
|
||||
break;
|
||||
@@ -98,7 +98,7 @@ bool Config::getStatus() {
|
||||
return this->status;
|
||||
}
|
||||
|
||||
bool Config::addGame(Game &game) {
|
||||
bool Config::addGame(Game &game, bool save) {
|
||||
tinyxml2::XMLNode *rootNode = this->configFile.LastChild();
|
||||
tinyxml2::XMLElement *gameNodes = rootNode->FirstChildElement("game");
|
||||
|
||||
@@ -162,15 +162,19 @@ bool Config::addGame(Game &game) {
|
||||
auto analogType = (int) BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0;
|
||||
int velocity_threshold = 0;
|
||||
bool invert = false;
|
||||
unsigned int modifier_mask = 0;
|
||||
tinyxml2::XMLError attrError = gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||
const char *devid = gameButtonNode->Attribute("devid");
|
||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||
gameButtonNode->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||
if (attrError != tinyxml2::XMLError::XML_SUCCESS) {
|
||||
gameButtonsNode->DeleteChild(gameButtonNode);
|
||||
gameButtonNode = this->configFile.NewElement("button");
|
||||
@@ -180,16 +184,20 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("devid", button->getDeviceIdentifier().c_str());
|
||||
gameButtonNode->SetAttribute("debounce_up", debounce_up);
|
||||
gameButtonNode->SetAttribute("debounce_down", debounce_down);
|
||||
gameButtonNode->SetAttribute("bat_threshold", bat_threshold);
|
||||
gameButtonNode->SetAttribute("velocity_threshold", velocity_threshold);
|
||||
gameButtonNode->SetAttribute("invert", invert);
|
||||
gameButtonNode->SetAttribute("modifiers", button->getModifierMask());
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
} else {
|
||||
button->setVKey(static_cast<unsigned short int>(vKey));
|
||||
button->setAnalogType((ButtonAnalogType) analogType);
|
||||
button->setDebounceUp(debounce_up);
|
||||
button->setDebounceDown(debounce_down);
|
||||
button->setBatThreshold(bat_threshold);
|
||||
button->setVelocityThreshold(velocity_threshold);
|
||||
button->setInvert(invert);
|
||||
button->setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||
if (devid) {
|
||||
button->setDeviceIdentifier(devid);
|
||||
}
|
||||
@@ -207,8 +215,10 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("analogtype", (int) it.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||
gameButtonNode->SetAttribute("modifiers", it.getModifierMask());
|
||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
}
|
||||
@@ -248,7 +258,7 @@ bool Config::addGame(Game &game) {
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
int delay_buffer_depth = 0;
|
||||
uint32_t delay = 0;
|
||||
tinyxml2::XMLError err1 = gameAnalogNode->QueryIntAttribute("index", &index);
|
||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||
@@ -257,7 +267,7 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
|
||||
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
|
||||
const char *devid = gameAnalogNode->Attribute("devid");
|
||||
|
||||
if (err1 != tinyxml2::XMLError::XML_SUCCESS || !devid) {
|
||||
@@ -273,7 +283,7 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
} else {
|
||||
it.setIndex(static_cast<unsigned short int>(index));
|
||||
@@ -285,7 +295,7 @@ bool Config::addGame(Game &game) {
|
||||
it.setSmoothing(smoothing);
|
||||
it.setMultiplier(multiplier);
|
||||
it.setRelativeMode(relative_mode);
|
||||
it.setDelayBufferDepth(delay_buffer_depth);
|
||||
it.setDelayMs(delay);
|
||||
}
|
||||
} else {
|
||||
gameAnalogNode = this->configFile.NewElement("analog");
|
||||
@@ -298,7 +308,7 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
@@ -432,8 +442,10 @@ bool Config::addGame(Game &game) {
|
||||
gameButtonNode->SetAttribute("analogtype", it.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", it.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", it.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", it.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", it.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", it.getInvert());
|
||||
gameButtonNode->SetAttribute("modifiers", it.getModifierMask());
|
||||
gameButtonNode->SetAttribute("devid", it.getDeviceIdentifier().c_str());
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
}
|
||||
@@ -452,7 +464,7 @@ bool Config::addGame(Game &game) {
|
||||
gameAnalogNode->SetAttribute("smoothing", it.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", it.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", it.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", it.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", it.getDelayMs());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
|
||||
@@ -480,13 +492,20 @@ bool Config::addGame(Game &game) {
|
||||
rootNode->InsertEndChild(gameNode);
|
||||
}
|
||||
|
||||
// save config
|
||||
this->saveConfigFile();
|
||||
// save config (skipped when caller batches multiple addGame calls
|
||||
// and flushes once at the end via Config::save())
|
||||
if (save) {
|
||||
this->saveConfigFile();
|
||||
}
|
||||
|
||||
// return success
|
||||
return true;
|
||||
}
|
||||
|
||||
void Config::save() {
|
||||
this->saveConfigFile();
|
||||
}
|
||||
|
||||
bool Config::updateBinding(const Game &game, const Button &button, int alternative) {
|
||||
|
||||
// get root node
|
||||
@@ -535,9 +554,11 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
gameButtonNode->SetAttribute("analogtype", (int) button.getAnalogType());
|
||||
gameButtonNode->SetAttribute("debounce_up", button.getDebounceUp());
|
||||
gameButtonNode->SetAttribute("debounce_down", button.getDebounceDown());
|
||||
gameButtonNode->SetAttribute("bat_threshold", button.getBatThreshold());
|
||||
gameButtonNode->SetAttribute("velocity_threshold", button.getVelocityThreshold());
|
||||
gameButtonNode->SetAttribute("invert", button.getInvert());
|
||||
gameButtonNode->SetAttribute("devid", button.getDeviceIdentifier().c_str());
|
||||
gameButtonNode->SetAttribute("modifiers", button.getModifierMask());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -551,9 +572,11 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
|
||||
gameButtonNode->SetAttribute("analogtype", 0);
|
||||
gameButtonNode->SetAttribute("debounce_up", 0.0);
|
||||
gameButtonNode->SetAttribute("debounce_down", 0.0);
|
||||
gameButtonNode->SetAttribute("bat_threshold", 0);
|
||||
gameButtonNode->SetAttribute("velocity_threshold", 0);
|
||||
gameButtonNode->SetAttribute("invert", false);
|
||||
gameButtonNode->SetAttribute("devid", "");
|
||||
gameButtonNode->SetAttribute("modifiers", 0);
|
||||
gameButtonsNode->InsertEndChild(gameButtonNode);
|
||||
}
|
||||
}
|
||||
@@ -684,7 +707,7 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||
} else {
|
||||
gameAnalogNode = this->configFile.NewElement("analog");
|
||||
@@ -696,7 +719,7 @@ bool Config::updateBinding(const Game &game, const Analog &analog) {
|
||||
gameAnalogNode->SetAttribute("smoothing", analog.getSmoothing());
|
||||
gameAnalogNode->SetAttribute("multiplier", analog.getMultiplier());
|
||||
gameAnalogNode->SetAttribute("relative", analog.isRelativeMode());
|
||||
gameAnalogNode->SetAttribute("delay", analog.getDelayBufferDepth());
|
||||
gameAnalogNode->SetAttribute("delay_ms", analog.getDelayMs());
|
||||
gameAnalogNode->SetAttribute("devid", analog.getDeviceIdentifier().c_str());
|
||||
gameAnalogsNode->InsertEndChild(gameAnalogNode);
|
||||
}
|
||||
@@ -946,14 +969,18 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
auto analogType = (int) BAT_NONE;
|
||||
double debounce_up = 0.0;
|
||||
double debounce_down = 0.0;
|
||||
int bat_threshold = 0;
|
||||
int velocity_threshold = 0;
|
||||
bool invert = false;
|
||||
unsigned int modifier_mask = 0;
|
||||
gameButtonNode->QueryIntAttribute("vkey", &vKey);
|
||||
gameButtonNode->QueryIntAttribute("analogtype", &analogType);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_up", &debounce_up);
|
||||
gameButtonNode->QueryDoubleAttribute("debounce_down", &debounce_down);
|
||||
gameButtonNode->QueryIntAttribute("bat_threshold", &bat_threshold);
|
||||
gameButtonNode->QueryIntAttribute("velocity_threshold", &velocity_threshold);
|
||||
gameButtonNode->QueryBoolAttribute("invert", &invert);
|
||||
gameButtonNode->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||
const char *devid = gameButtonNode->Attribute("devid");
|
||||
|
||||
// find alternative
|
||||
@@ -966,8 +993,10 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
alt.setAnalogType((ButtonAnalogType) analogType);
|
||||
alt.setDebounceUp(debounce_up);
|
||||
alt.setDebounceDown(debounce_down);
|
||||
alt.setBatThreshold(bat_threshold);
|
||||
alt.setVelocityThreshold(velocity_threshold);
|
||||
alt.setInvert(invert);
|
||||
alt.setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||
if (devid) {
|
||||
alt.setDeviceIdentifier(std::string(devid));
|
||||
}
|
||||
@@ -984,8 +1013,10 @@ std::vector<Button> Config::getButtons(const std::string &gameName) {
|
||||
button.setAnalogType((ButtonAnalogType) analogType);
|
||||
button.setDebounceUp(debounce_up);
|
||||
button.setDebounceDown(debounce_down);
|
||||
button.setBatThreshold(bat_threshold);
|
||||
button.setVelocityThreshold(velocity_threshold);
|
||||
button.setInvert(invert);
|
||||
button.setModifierMask(static_cast<uint8_t>(modifier_mask));
|
||||
if (devid) {
|
||||
button.setDeviceIdentifier(devid);
|
||||
}
|
||||
@@ -1130,7 +1161,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
bool smoothing = false;
|
||||
int multiplier = 1;
|
||||
bool relative_mode = false;
|
||||
int delay_buffer_depth = 0;
|
||||
uint32_t delay = 0;
|
||||
gameAnalogNode->QueryIntAttribute("index", &index);
|
||||
gameAnalogNode->QueryFloatAttribute("sensivity", &sensitivity);
|
||||
gameAnalogNode->QueryFloatAttribute("deadzone", &deadzone);
|
||||
@@ -1139,7 +1170,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
gameAnalogNode->QueryBoolAttribute("smoothing", &smoothing);
|
||||
gameAnalogNode->QueryIntAttribute("multiplier", &multiplier);
|
||||
gameAnalogNode->QueryBoolAttribute("relative", &relative_mode);
|
||||
gameAnalogNode->QueryIntAttribute("delay", &delay_buffer_depth);
|
||||
gameAnalogNode->QueryUnsignedAttribute("delay_ms", &delay);
|
||||
const char *devid = gameAnalogNode->Attribute("devid");
|
||||
|
||||
// create analog and add to list
|
||||
@@ -1152,7 +1183,7 @@ std::vector<Analog> Config::getAnalogs(const std::string &gameName) {
|
||||
analog.setSmoothing(smoothing);
|
||||
analog.setMultiplier(multiplier);
|
||||
analog.setRelativeMode(relative_mode);
|
||||
analog.setDelayBufferDepth(delay_buffer_depth);
|
||||
analog.setDelayMs(delay);
|
||||
if (devid) {
|
||||
analog.setDeviceIdentifier(devid);
|
||||
}
|
||||
@@ -1312,17 +1343,32 @@ bool Config::firstFillConfigFile() {
|
||||
}
|
||||
|
||||
void Config::saveConfigFile() {
|
||||
// create a .tmp file and write to it...
|
||||
// write the new config to a .tmp file first...
|
||||
const auto xml_result = this->configFile.SaveFile(this->configLocationTemp.c_str(), false);
|
||||
if (xml_result != tinyxml2::XMLError::XML_SUCCESS) {
|
||||
log_info("cfg", "failed to write file: {}", this->configLocationTemp.string());
|
||||
log_info("cfg", "failed to write file: {}", this->configLocationTemp);
|
||||
return;
|
||||
}
|
||||
// copy the .tmp file to the main file...
|
||||
if (CopyFileW(this->configLocationTemp.c_str(), this->configLocation.c_str(), false) == 0) {
|
||||
log_warning("cfg", "CopyFileA failed: 0x{:08x}", GetLastError());
|
||||
|
||||
// ...flush the .tmp file to disk so a crash/power loss can't leave it half-written...
|
||||
HANDLE tmp_handle = CreateFileW(
|
||||
this->configLocationTemp.c_str(),
|
||||
GENERIC_WRITE, FILE_SHARE_READ, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (tmp_handle != INVALID_HANDLE_VALUE) {
|
||||
FlushFileBuffers(tmp_handle);
|
||||
CloseHandle(tmp_handle);
|
||||
}
|
||||
|
||||
// ...then atomically replace the real config with the .tmp file.
|
||||
// Unlike CopyFile (which truncates and rewrites the destination in place),
|
||||
// an NTFS rename is atomic: the existing config is never left half-overwritten,
|
||||
// so an interrupted save can't corrupt it. MoveFileEx also removes the .tmp on success.
|
||||
if (MoveFileExW(
|
||||
this->configLocationTemp.c_str(),
|
||||
this->configLocation.c_str(),
|
||||
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0) {
|
||||
log_warning("cfg", "MoveFileExW failed: 0x{:08x}", GetLastError());
|
||||
return;
|
||||
}
|
||||
// delete the .tmp file (not critical if this fails)
|
||||
DeleteFileW(this->configLocationTemp.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,12 @@ public:
|
||||
bool getStatus();
|
||||
bool createConfigFile();
|
||||
|
||||
bool addGame(Game &game);
|
||||
bool addGame(Game &game, bool save = true);
|
||||
|
||||
// flush pending in-memory changes to disk; intended to be paired with
|
||||
// addGame(game, false) in bulk-init paths so we write the XML once instead
|
||||
// of once per game
|
||||
void save();
|
||||
|
||||
bool updateBinding(const Game &game, const Button &button, int alternative);
|
||||
bool updateBinding(const Game &game, const Analog &analog);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#include "configurator.h"
|
||||
#include "configurator.h"
|
||||
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace cfg {
|
||||
|
||||
// globals
|
||||
bool CONFIGURATOR_STANDALONE = false;
|
||||
bool CONFIGURATOR_FORCE_SOFTWARE_RENDER = false;
|
||||
ConfigType CONFIGURATOR_TYPE = ConfigType::Config;
|
||||
|
||||
Configurator::Configurator() {
|
||||
@@ -16,13 +21,98 @@ namespace cfg {
|
||||
CONFIGURATOR_STANDALONE = false;
|
||||
}
|
||||
|
||||
// Attempt to bring up a D3D9 device backing the configurator's window so the
|
||||
// overlay can use the hardware-accelerated imgui_impl_dx9 path instead of
|
||||
// the CPU rasterizer. Returns true if both Direct3DCreate9 and CreateDevice
|
||||
// succeed; the caller falls back to overlay::create_software() otherwise so
|
||||
// that environments without D3D9 (Wine without dxvk, headless test boxes,
|
||||
// GPUs whose drivers reject HAL) still get a working configurator.
|
||||
static bool try_init_d3d9(ConfiguratorWindow &wnd) {
|
||||
if (cfg::CONFIGURATOR_FORCE_SOFTWARE_RENDER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wnd.hWnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// load d3d9.dll dynamically rather than linking against it statically.
|
||||
// a static import would force the system d3d9.dll to load at process
|
||||
// startup for the main spice executable too (this file is shared with
|
||||
// spice.exe), which loads system32\d3d9.dll before the modules directory
|
||||
// is added to the DLL search path - preventing a user-supplied DXVK
|
||||
// d3d9.dll in modules from ever loading for the game.
|
||||
typedef IDirect3D9 *(WINAPI *Direct3DCreate9_t)(UINT);
|
||||
HMODULE d3d9_module = libutils::try_library("d3d9.dll");
|
||||
if (d3d9_module == nullptr) {
|
||||
log_warning("configurator", "could not load d3d9.dll, falling back to software renderer");
|
||||
return false;
|
||||
}
|
||||
auto Direct3DCreate9_fn =
|
||||
reinterpret_cast<Direct3DCreate9_t>(libutils::try_proc(d3d9_module, "Direct3DCreate9"));
|
||||
if (Direct3DCreate9_fn == nullptr) {
|
||||
log_warning("configurator", "could not find Direct3DCreate9, falling back to software renderer");
|
||||
return false;
|
||||
}
|
||||
|
||||
IDirect3D9 *d3d = Direct3DCreate9_fn(D3D_SDK_VERSION);
|
||||
if (d3d == nullptr) {
|
||||
log_warning("configurator", "Direct3DCreate9 returned NULL, falling back to software renderer");
|
||||
return false;
|
||||
}
|
||||
|
||||
D3DPRESENT_PARAMETERS pp {};
|
||||
pp.Windowed = TRUE;
|
||||
pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||
// D3DFMT_UNKNOWN -> driver picks the current desktop format
|
||||
pp.BackBufferFormat = D3DFMT_UNKNOWN;
|
||||
pp.BackBufferWidth = static_cast<UINT>(wnd.client_width);
|
||||
pp.BackBufferHeight = static_cast<UINT>(wnd.client_height);
|
||||
pp.hDeviceWindow = wnd.hWnd;
|
||||
pp.EnableAutoDepthStencil = FALSE;
|
||||
pp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
|
||||
|
||||
IDirect3DDevice9 *device = nullptr;
|
||||
// SOFTWARE_VERTEXPROCESSING is the most compatible behavior flag; the UI
|
||||
// is tiny so we don't need hardware T&L. FPU_PRESERVE keeps our floating
|
||||
// point environment intact in case other spice code relies on it.
|
||||
const DWORD behavior_flags = D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE;
|
||||
HRESULT hr = d3d->CreateDevice(
|
||||
D3DADAPTER_DEFAULT,
|
||||
D3DDEVTYPE_HAL,
|
||||
wnd.hWnd,
|
||||
behavior_flags,
|
||||
&pp,
|
||||
&device);
|
||||
if (FAILED(hr) || device == nullptr) {
|
||||
log_warning("configurator",
|
||||
"D3D9 CreateDevice failed (hr={:#x}), falling back to software renderer",
|
||||
static_cast<unsigned int>(hr));
|
||||
d3d->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
wnd.d3d = d3d;
|
||||
wnd.device = device;
|
||||
wnd.pp = pp;
|
||||
wnd.use_d3d9 = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Configurator::run() {
|
||||
|
||||
// create instance
|
||||
// bring up the overlay against either a real D3D9 device or the software
|
||||
// rasterizer. The choice is one-shot - the in-game overlay always uses
|
||||
// the same renderer the configurator picked here.
|
||||
overlay::ENABLED = true;
|
||||
overlay::create_software(this->wnd.hWnd);
|
||||
if (try_init_d3d9(this->wnd)) {
|
||||
log_info("configurator", "using D3D9 hardware-accelerated renderer");
|
||||
overlay::create_d3d9(this->wnd.hWnd, this->wnd.d3d, this->wnd.device);
|
||||
} else {
|
||||
log_info("configurator", "using software renderer");
|
||||
overlay::create_software(this->wnd.hWnd);
|
||||
}
|
||||
overlay::OVERLAY->set_active(true);
|
||||
overlay::OVERLAY->hotkeys_enable = false;
|
||||
ImGui::GetIO().MouseDrawCursor = false;
|
||||
|
||||
// run window
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace cfg {
|
||||
// globals
|
||||
extern bool CONFIGURATOR_STANDALONE;
|
||||
extern ConfigType CONFIGURATOR_TYPE;
|
||||
extern bool CONFIGURATOR_FORCE_SOFTWARE_RENDER;
|
||||
|
||||
class Configurator {
|
||||
private:
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#include "configurator_wnd.h"
|
||||
#include "configurator_wnd.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include "build/defs.h"
|
||||
#include "external/imgui/imgui.h"
|
||||
#include "launcher/shutdown.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/precise_timer.h"
|
||||
#include "util/utils.h"
|
||||
#include "cfg/configurator.h"
|
||||
|
||||
#include "icon.h"
|
||||
@@ -14,8 +21,177 @@ static const char *CLASS_NAME = "ConfiguratorWindow";
|
||||
static std::string WINDOW_TITLE;
|
||||
static int WINDOW_SIZE_X = 800;
|
||||
static int WINDOW_SIZE_Y = 600;
|
||||
static const int WINDOW_MIN_SIZE_X = 540;
|
||||
static const int WINDOW_MIN_SIZE_Y = 300;
|
||||
static HICON WINDOW_ICON = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(MAINICON));
|
||||
|
||||
static const UINT_PTR RENDER_TIMER_ID = 1;
|
||||
|
||||
// Map a virtual key code to the matching ImGuiKey. Mirrors the table used by
|
||||
// the in-game ImGui spice backend so navigation keys behave identically when
|
||||
// the configurator runs standalone and drives ImGui from Win32 messages.
|
||||
static ImGuiKey vk_to_imgui_key(WPARAM vkey) {
|
||||
switch (vkey) {
|
||||
case VK_TAB: return ImGuiKey_Tab;
|
||||
case VK_LEFT: return ImGuiKey_LeftArrow;
|
||||
case VK_RIGHT: return ImGuiKey_RightArrow;
|
||||
case VK_UP: return ImGuiKey_UpArrow;
|
||||
case VK_DOWN: return ImGuiKey_DownArrow;
|
||||
case VK_PRIOR: return ImGuiKey_PageUp;
|
||||
case VK_NEXT: return ImGuiKey_PageDown;
|
||||
case VK_HOME: return ImGuiKey_Home;
|
||||
case VK_END: return ImGuiKey_End;
|
||||
case VK_INSERT: return ImGuiKey_Insert;
|
||||
case VK_DELETE: return ImGuiKey_Delete;
|
||||
case VK_BACK: return ImGuiKey_Backspace;
|
||||
case VK_SPACE: return ImGuiKey_Space;
|
||||
case VK_RETURN: return ImGuiKey_Enter;
|
||||
case VK_ESCAPE: return ImGuiKey_Escape;
|
||||
case VK_LSHIFT: return ImGuiKey_LeftShift;
|
||||
case VK_RSHIFT: return ImGuiKey_RightShift;
|
||||
case VK_SHIFT: return ImGuiKey_LeftShift;
|
||||
case VK_LCONTROL: return ImGuiKey_LeftCtrl;
|
||||
case VK_RCONTROL: return ImGuiKey_RightCtrl;
|
||||
case VK_CONTROL: return ImGuiKey_LeftCtrl;
|
||||
case 'A': return ImGuiKey_A;
|
||||
case 'C': return ImGuiKey_C;
|
||||
case 'V': return ImGuiKey_V;
|
||||
case 'X': return ImGuiKey_X;
|
||||
case 'Y': return ImGuiKey_Y;
|
||||
case 'Z': return ImGuiKey_Z;
|
||||
default: return ImGuiKey_None;
|
||||
}
|
||||
}
|
||||
|
||||
static ImGuiKey vk_to_imgui_mod_key(WPARAM vkey) {
|
||||
switch (vkey) {
|
||||
case VK_SHIFT:
|
||||
case VK_LSHIFT:
|
||||
case VK_RSHIFT:
|
||||
return ImGuiMod_Shift;
|
||||
case VK_CONTROL:
|
||||
case VK_LCONTROL:
|
||||
case VK_RCONTROL:
|
||||
return ImGuiMod_Ctrl;
|
||||
case VK_MENU:
|
||||
case VK_LMENU:
|
||||
case VK_RMENU:
|
||||
return ImGuiMod_Alt;
|
||||
default:
|
||||
return ImGuiMod_None;
|
||||
}
|
||||
}
|
||||
|
||||
static cfg::ConfiguratorWindow *get_state(HWND hWnd) {
|
||||
return reinterpret_cast<cfg::ConfiguratorWindow *>(
|
||||
GetWindowLongPtrW(hWnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
// computes the top-left position that centers a window of the given size on
|
||||
// the primary monitor's work area (the desktop minus the taskbar); falls back
|
||||
// to (0, 0) if the monitor info can't be queried.
|
||||
static POINT center_on_primary_monitor(int width, int height) {
|
||||
POINT pos = { 0, 0 };
|
||||
HMONITOR mon = MonitorFromPoint(pos, MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFO mi {};
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfo(mon, &mi)) {
|
||||
const int work_w = mi.rcWork.right - mi.rcWork.left;
|
||||
const int work_h = mi.rcWork.bottom - mi.rcWork.top;
|
||||
pos.x = mi.rcWork.left + (work_w - width) / 2;
|
||||
pos.y = mi.rcWork.top + (work_h - height) / 2;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Returns the refresh rate (Hz) of the monitor the window currently lives on,
|
||||
// clamped to a sane range. Falls back to 60 if detection fails or the value
|
||||
// looks invalid (DEVMODE may report 0 or 1 to mean "use hardware default").
|
||||
static UINT detect_monitor_refresh_hz(HWND hWnd) {
|
||||
UINT hz = 0;
|
||||
HMONITOR mon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
|
||||
if (mon) {
|
||||
MONITORINFOEXW mi {};
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfoW(mon, reinterpret_cast<LPMONITORINFO>(&mi))) {
|
||||
DEVMODEW dm {};
|
||||
dm.dmSize = sizeof(dm);
|
||||
if (EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm)) {
|
||||
hz = dm.dmDisplayFrequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hz <= 1) {
|
||||
HDC hdc = GetDC(hWnd);
|
||||
if (hdc) {
|
||||
int v = GetDeviceCaps(hdc, VREFRESH);
|
||||
if (v > 1) {
|
||||
hz = static_cast<UINT>(v);
|
||||
}
|
||||
ReleaseDC(hWnd, hdc);
|
||||
}
|
||||
}
|
||||
if (hz < 30 || hz > 240) {
|
||||
hz = 60;
|
||||
}
|
||||
return hz;
|
||||
}
|
||||
|
||||
// Software-path WM_PAINT: blit the overlay's pixel buffer to the window using
|
||||
// SetDIBitsToDevice. Avoids creating/destroying a GDI HBITMAP every frame.
|
||||
static void paint_software(HWND hWnd) {
|
||||
if (!overlay::OVERLAY) {
|
||||
PAINTSTRUCT ps {};
|
||||
BeginPaint(hWnd, &ps);
|
||||
EndPaint(hWnd, &ps);
|
||||
return;
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
uint32_t *pixel_data = overlay::OVERLAY->sw_get_pixel_data(&width, &height);
|
||||
|
||||
PAINTSTRUCT paint {};
|
||||
HDC hdc = BeginPaint(hWnd, &paint);
|
||||
|
||||
if (pixel_data && width > 0 && height > 0) {
|
||||
BITMAPINFO bmi {};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = width;
|
||||
// negative height -> top-down DIB, matching how ImGui packs pixels
|
||||
bmi.bmiHeader.biHeight = -height;
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
SetDIBitsToDevice(
|
||||
hdc,
|
||||
0, 0,
|
||||
width, height,
|
||||
0, 0,
|
||||
0, height,
|
||||
pixel_data,
|
||||
&bmi,
|
||||
DIB_RGB_COLORS);
|
||||
}
|
||||
|
||||
EndPaint(hWnd, &paint);
|
||||
}
|
||||
|
||||
void cfg::ConfiguratorWindow::start_timer() {
|
||||
if (!this->timer_running && this->hWnd) {
|
||||
SetTimer(this->hWnd, RENDER_TIMER_ID, this->timer_interval_ms, nullptr);
|
||||
this->timer_running = true;
|
||||
}
|
||||
}
|
||||
|
||||
void cfg::ConfiguratorWindow::stop_timer() {
|
||||
if (this->timer_running && this->hWnd) {
|
||||
KillTimer(this->hWnd, RENDER_TIMER_ID);
|
||||
this->timer_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
cfg::ConfiguratorWindow::ConfiguratorWindow() {
|
||||
|
||||
// register the window class
|
||||
@@ -27,12 +203,22 @@ cfg::ConfiguratorWindow::ConfiguratorWindow() {
|
||||
wc.hIcon = WINDOW_ICON;
|
||||
RegisterClass(&wc);
|
||||
|
||||
// raise SetTimer resolution so high refresh rates (120/144Hz) are actually
|
||||
// achievable. Without this, USER timers quantize to ~15.6ms and cap us
|
||||
// near ~64 FPS. Uses the shared helper so it respects -notimerhacks
|
||||
// (Use Legacy Timers) and the Win11 timer-throttling opt-out. The helper
|
||||
// intentionally never calls timeEndPeriod; the kernel releases the request
|
||||
// when spicecfg exits.
|
||||
timeutils::set_timer_resolution();
|
||||
|
||||
// determine window title
|
||||
if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
|
||||
WINDOW_TITLE = "spice2x config (" + to_string(VERSION_STRING_CFG) + ")";
|
||||
WINDOW_SIZE_X = 800;
|
||||
WINDOW_SIZE_Y = 600;
|
||||
}
|
||||
this->client_width = WINDOW_SIZE_X;
|
||||
this->client_height = WINDOW_SIZE_Y;
|
||||
|
||||
// open window
|
||||
this->hWnd = CreateWindowEx(
|
||||
@@ -48,27 +234,58 @@ cfg::ConfiguratorWindow::ConfiguratorWindow() {
|
||||
|
||||
if (this->hWnd) {
|
||||
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
|
||||
// force dark title bar
|
||||
set_window_dark_titlebar(this->hWnd, true);
|
||||
}
|
||||
}
|
||||
|
||||
cfg::ConfiguratorWindow::~ConfiguratorWindow() {
|
||||
|
||||
this->stop_timer();
|
||||
|
||||
// close window
|
||||
DestroyWindow(this->hWnd);
|
||||
|
||||
// unregister class
|
||||
UnregisterClass(CLASS_NAME, GetModuleHandle(NULL));
|
||||
|
||||
// release D3D9 resources if any
|
||||
if (this->device) {
|
||||
this->device->Release();
|
||||
this->device = nullptr;
|
||||
}
|
||||
if (this->d3d) {
|
||||
this->d3d->Release();
|
||||
this->d3d = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void cfg::ConfiguratorWindow::run() {
|
||||
|
||||
const POINT pos = center_on_primary_monitor(WINDOW_SIZE_X, WINDOW_SIZE_Y);
|
||||
SetWindowPos(this->hWnd, HWND_TOP, pos.x, pos.y, WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
|
||||
|
||||
// show window
|
||||
SetWindowPos(this->hWnd, HWND_TOP, 0, 0, WINDOW_SIZE_X, WINDOW_SIZE_Y, 0);
|
||||
ShowWindow(this->hWnd, SW_SHOWNORMAL);
|
||||
UpdateWindow(this->hWnd);
|
||||
|
||||
// draw overlay in 60 FPS
|
||||
SetTimer(this->hWnd, 1, 1000 / 60, nullptr);
|
||||
// SW_SHOWNORMAL usually activates a top-level window, but not always (e.g.,
|
||||
// when the launching process doesn't have foreground rights to transfer).
|
||||
// Force foreground+focus explicitly so WM_MOUSEWHEEL is routed to us from
|
||||
// the very first frame; the Win32 default routes wheel events to the
|
||||
// keyboard-focused window.
|
||||
SetForegroundWindow(this->hWnd);
|
||||
SetFocus(this->hWnd);
|
||||
|
||||
// match the render timer to the monitor refresh rate so scrolling stays smooth
|
||||
// on 60/120/144 Hz panels. Idle CPU is bounded by overlay::sw_pixels_dirty
|
||||
// (software path) and overlay::d3d9_frame_dirty (D3D9 path); the timer is also
|
||||
// paused entirely when the window is minimized (see WM_SIZE handler).
|
||||
const UINT hz = detect_monitor_refresh_hz(this->hWnd);
|
||||
this->timer_interval_ms = std::max<UINT>(1, 1000 / hz);
|
||||
log_info("configurator", "render timer {} ms ({} Hz)",
|
||||
this->timer_interval_ms, hz);
|
||||
this->start_timer();
|
||||
|
||||
// window loop
|
||||
BOOL ret;
|
||||
@@ -94,6 +311,22 @@ LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPAR
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_GETMINMAXINFO: {
|
||||
|
||||
// enforce a minimum window size so the UI can't be shrunk to a
|
||||
// point where the tabs/controls become unusable. The minimum is
|
||||
// expressed in client-area pixels and converted to a full window
|
||||
// size that accounts for the current frame/title-bar style.
|
||||
RECT rc = { 0, 0, WINDOW_MIN_SIZE_X, WINDOW_MIN_SIZE_Y };
|
||||
DWORD style = static_cast<DWORD>(GetWindowLongPtrW(hWnd, GWL_STYLE));
|
||||
DWORD ex_style = static_cast<DWORD>(GetWindowLongPtrW(hWnd, GWL_EXSTYLE));
|
||||
if (AdjustWindowRectEx(&rc, style, FALSE, ex_style)) {
|
||||
auto *mmi = reinterpret_cast<MINMAXINFO *>(lParam);
|
||||
mmi->ptMinTrackSize.x = rc.right - rc.left;
|
||||
mmi->ptMinTrackSize.y = rc.bottom - rc.top;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_CREATE: {
|
||||
|
||||
// set user data of window to class pointer
|
||||
@@ -102,6 +335,72 @@ LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPAR
|
||||
|
||||
break;
|
||||
}
|
||||
case WM_SIZE: {
|
||||
|
||||
// pause/resume the render timer based on visibility
|
||||
auto *state = get_state(hWnd);
|
||||
if (state) {
|
||||
if (wParam == SIZE_MINIMIZED) {
|
||||
state->window_minimized = true;
|
||||
state->stop_timer();
|
||||
} else {
|
||||
if (state->window_minimized) {
|
||||
state->window_minimized = false;
|
||||
// force a full repaint on the next frame
|
||||
state->has_valid_draw_hash = false;
|
||||
state->start_timer();
|
||||
}
|
||||
const int new_w = LOWORD(lParam);
|
||||
const int new_h = HIWORD(lParam);
|
||||
if (new_w > 0 && new_h > 0
|
||||
&& (new_w != state->client_width || new_h != state->client_height)) {
|
||||
state->client_width = new_w;
|
||||
state->client_height = new_h;
|
||||
|
||||
// reset the D3D9 device with the new back-buffer size so the
|
||||
// hardware-accelerated path matches the window dimensions.
|
||||
if (state->use_d3d9 && state->device) {
|
||||
if (overlay::OVERLAY) {
|
||||
overlay::OVERLAY->reset_invalidate();
|
||||
}
|
||||
state->pp.BackBufferWidth = static_cast<UINT>(new_w);
|
||||
state->pp.BackBufferHeight = static_cast<UINT>(new_h);
|
||||
HRESULT hr = state->device->Reset(&state->pp);
|
||||
if (FAILED(hr)) {
|
||||
log_warning("configurator", "D3D9 device Reset failed, hr={:#x}",
|
||||
static_cast<unsigned int>(hr));
|
||||
}
|
||||
if (overlay::OVERLAY) {
|
||||
overlay::OVERLAY->reset_recreate();
|
||||
}
|
||||
}
|
||||
|
||||
// force a full repaint after resize
|
||||
state->has_valid_draw_hash = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_DISPLAYCHANGE: {
|
||||
|
||||
// monitor refresh rate may have changed (or the window moved to a
|
||||
// different monitor); re-detect and re-arm the render timer.
|
||||
auto *state = get_state(hWnd);
|
||||
if (state && state->timer_running) {
|
||||
const UINT hz = detect_monitor_refresh_hz(hWnd);
|
||||
const UINT new_interval = std::max<UINT>(1, 1000 / hz);
|
||||
if (new_interval != state->timer_interval_ms) {
|
||||
state->stop_timer();
|
||||
state->timer_interval_ms = new_interval;
|
||||
state->start_timer();
|
||||
log_info("configurator",
|
||||
"WM_DISPLAYCHANGE: render timer {} ms ({} Hz)",
|
||||
new_interval, hz);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_CLOSE:
|
||||
case WM_DESTROY: {
|
||||
|
||||
@@ -110,62 +409,192 @@ LRESULT CALLBACK cfg::ConfiguratorWindow::window_proc(HWND hWnd, UINT uMsg, WPAR
|
||||
break;
|
||||
}
|
||||
case WM_TIMER: {
|
||||
if (wParam != RENDER_TIMER_ID) {
|
||||
break;
|
||||
}
|
||||
|
||||
// update overlay
|
||||
auto *state = get_state(hWnd);
|
||||
|
||||
// skip rendering when the window is minimized or hidden - the timer is
|
||||
// already paused on minimize, but defensively skip here as well.
|
||||
if (state && (state->window_minimized || !IsWindowVisible(hWnd))) {
|
||||
break;
|
||||
}
|
||||
|
||||
const bool use_d3d9 = state && state->use_d3d9 && state->device;
|
||||
|
||||
// build the imgui frame (input is already buffered via WM_* messages)
|
||||
if (overlay::OVERLAY) {
|
||||
overlay::OVERLAY->update();
|
||||
overlay::OVERLAY->set_active(true);
|
||||
overlay::OVERLAY->new_frame();
|
||||
overlay::OVERLAY->render();
|
||||
}
|
||||
|
||||
// repaint window
|
||||
InvalidateRect(hWnd, nullptr, TRUE);
|
||||
if (use_d3d9) {
|
||||
const HRESULT cl = state->device->TestCooperativeLevel();
|
||||
if (cl == D3DERR_DEVICELOST) {
|
||||
break;
|
||||
}
|
||||
if (cl == D3DERR_DEVICENOTRESET) {
|
||||
if (overlay::OVERLAY) {
|
||||
overlay::OVERLAY->reset_invalidate();
|
||||
}
|
||||
const HRESULT reset_hr = state->device->Reset(&state->pp);
|
||||
if (SUCCEEDED(reset_hr) && overlay::OVERLAY) {
|
||||
overlay::OVERLAY->reset_recreate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (FAILED(cl)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (overlay::OVERLAY) {
|
||||
overlay::OVERLAY->render();
|
||||
}
|
||||
|
||||
// skip Clear/Present when ImGui draw data is unchanged; the last
|
||||
// presented frame stays visible (same fast path as sw_pixels_dirty).
|
||||
const bool dirty = overlay::OVERLAY && overlay::OVERLAY->d3d9_frame_dirty;
|
||||
const bool force_present = state && !state->has_valid_draw_hash;
|
||||
if (dirty || force_present) {
|
||||
if (state) {
|
||||
state->has_valid_draw_hash = true;
|
||||
}
|
||||
state->device->Clear(0, nullptr, D3DCLEAR_TARGET,
|
||||
D3DCOLOR_RGBA(20, 18, 18, 255), 1.0f, 0);
|
||||
if (SUCCEEDED(state->device->BeginScene())) {
|
||||
if (overlay::OVERLAY) {
|
||||
overlay::OVERLAY->d3d9_render_draw(force_present);
|
||||
}
|
||||
state->device->EndScene();
|
||||
}
|
||||
const HRESULT hr = state->device->Present(nullptr, nullptr, nullptr, nullptr);
|
||||
if (hr == D3DERR_DEVICELOST) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (overlay::OVERLAY) {
|
||||
overlay::OVERLAY->render();
|
||||
}
|
||||
// software path: the overlay's renderer already skipped the costly
|
||||
// paint_imgui call when the draw data was unchanged. Only invalidate
|
||||
// the window when those pixels actually changed, or on the first
|
||||
// frame / after a resize when we lost the previously cached state.
|
||||
const bool dirty = overlay::OVERLAY && overlay::OVERLAY->sw_pixels_dirty;
|
||||
const bool force_repaint = state && !state->has_valid_draw_hash;
|
||||
if (dirty || force_repaint) {
|
||||
if (state) {
|
||||
state->has_valid_draw_hash = true;
|
||||
}
|
||||
// pass FALSE for bErase - WM_ERASEBKGND already short-circuits, so
|
||||
// skipping the erase region avoids one extra Win32 round trip.
|
||||
InvalidateRect(hWnd, nullptr, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND: {
|
||||
return 1;
|
||||
}
|
||||
case WM_PAINT: {
|
||||
|
||||
// render overlay
|
||||
if (overlay::OVERLAY) {
|
||||
|
||||
// get pixel data
|
||||
int width, height;
|
||||
uint32_t *pixel_data = overlay::OVERLAY->sw_get_pixel_data(&width, &height);
|
||||
if (width > 0 && height > 0) {
|
||||
|
||||
// create bitmap
|
||||
HBITMAP bitmap = CreateBitmap(width, height, 1, 8 * sizeof(uint32_t), pixel_data);
|
||||
|
||||
// prepare paint
|
||||
PAINTSTRUCT paint{};
|
||||
HDC hdc = BeginPaint(hWnd, &paint);
|
||||
HDC hdcMem = CreateCompatibleDC(hdc);
|
||||
SetBkMode(hdc, TRANSPARENT);
|
||||
|
||||
// draw bitmap
|
||||
SelectObject(hdcMem, bitmap);
|
||||
BitBlt(hdc, paint.rcPaint.left, paint.rcPaint.top,
|
||||
paint.rcPaint.right - paint.rcPaint.left,
|
||||
paint.rcPaint.bottom - paint.rcPaint.top,
|
||||
hdcMem, paint.rcPaint.left, paint.rcPaint.top, SRCCOPY);
|
||||
|
||||
// delete bitmap
|
||||
DeleteObject(bitmap);
|
||||
|
||||
// clean up
|
||||
DeleteDC(hdcMem);
|
||||
EndPaint(hWnd, &paint);
|
||||
} else {
|
||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
paint_software(hWnd);
|
||||
break;
|
||||
}
|
||||
case WM_ACTIVATE: {
|
||||
const WORD activation = LOWORD(wParam);
|
||||
if (activation == WA_INACTIVE) {
|
||||
// dropping any held mouse buttons so we don't get stuck in a
|
||||
// "button still pressed" state when we come back.
|
||||
auto &io = ImGui::GetIO();
|
||||
io.AddMouseButtonEvent(ImGuiMouseButton_Left, false);
|
||||
io.AddMouseButtonEvent(ImGuiMouseButton_Right, false);
|
||||
io.AddMouseButtonEvent(ImGuiMouseButton_Middle, false);
|
||||
} else {
|
||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||
// WA_ACTIVE or WA_CLICKACTIVE: make sure the keyboard focus is
|
||||
// actually on this window so WM_MOUSEWHEEL gets routed to us
|
||||
// again. Without this, scrolling silently stops working after
|
||||
// the user alt-tabs back to spicecfg.
|
||||
SetFocus(hWnd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_KILLFOCUS: {
|
||||
// mirror the WA_INACTIVE mouse-button drop for the keyboard -
|
||||
// without this, a key held during alt-tab stays "down" in ImGui
|
||||
// state until the user presses and releases it again.
|
||||
ImGui::GetIO().ClearInputKeys();
|
||||
break;
|
||||
}
|
||||
// input messages routed straight into ImGui IO. This replaces the previous
|
||||
// per-frame 256-VK rawinput scan in ImGui_ImplSpice_NewFrame for the
|
||||
// standalone configurator (see CONFIGURATOR_STANDALONE branches there).
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYUP: {
|
||||
if (wParam == VK_F4) {
|
||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
const bool down = (uMsg == WM_KEYDOWN) || (uMsg == WM_SYSKEYDOWN);
|
||||
auto &io = ImGui::GetIO();
|
||||
const ImGuiKey key = vk_to_imgui_key(wParam);
|
||||
if (key != ImGuiKey_None) {
|
||||
io.AddKeyEvent(key, down);
|
||||
}
|
||||
const ImGuiKey mod = vk_to_imgui_mod_key(wParam);
|
||||
if (mod != ImGuiMod_None) {
|
||||
io.AddKeyEvent(mod, down);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_MOUSEMOVE: {
|
||||
auto &io = ImGui::GetIO();
|
||||
io.AddMousePosEvent(static_cast<float>(GET_X_LPARAM(lParam)),
|
||||
static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
break;
|
||||
}
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_MBUTTONUP: {
|
||||
int button = 0;
|
||||
bool down = false;
|
||||
switch (uMsg) {
|
||||
case WM_LBUTTONDOWN: button = ImGuiMouseButton_Left; down = true; break;
|
||||
case WM_LBUTTONUP: button = ImGuiMouseButton_Left; down = false; break;
|
||||
case WM_RBUTTONDOWN: button = ImGuiMouseButton_Right; down = true; break;
|
||||
case WM_RBUTTONUP: button = ImGuiMouseButton_Right; down = false; break;
|
||||
case WM_MBUTTONDOWN: button = ImGuiMouseButton_Middle; down = true; break;
|
||||
case WM_MBUTTONUP: button = ImGuiMouseButton_Middle; down = false; break;
|
||||
}
|
||||
auto &io = ImGui::GetIO();
|
||||
io.AddMouseButtonEvent(button, down);
|
||||
if (down) {
|
||||
SetCapture(hWnd);
|
||||
} else {
|
||||
ReleaseCapture();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_MOUSEWHEEL: {
|
||||
const float delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wParam))
|
||||
/ static_cast<float>(WHEEL_DELTA);
|
||||
ImGui::GetIO().AddMouseWheelEvent(0.0f, delta);
|
||||
break;
|
||||
}
|
||||
#if !SPICE_XP
|
||||
case WM_MOUSEHWHEEL: {
|
||||
const float delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wParam))
|
||||
/ static_cast<float>(WHEEL_DELTA);
|
||||
ImGui::GetIO().AddMouseWheelEvent(delta, 0.0f);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d9.h>
|
||||
|
||||
namespace cfg {
|
||||
|
||||
@@ -9,11 +12,38 @@ namespace cfg {
|
||||
|
||||
HWND hWnd;
|
||||
|
||||
// optional D3D9 device backing the configurator window; nullptr when running
|
||||
// the software-rendered path. Owned by ConfiguratorWindow when set.
|
||||
IDirect3D9 *d3d = nullptr;
|
||||
IDirect3DDevice9 *device = nullptr;
|
||||
D3DPRESENT_PARAMETERS pp {};
|
||||
bool use_d3d9 = false;
|
||||
|
||||
// throttling / pause state. timer_interval_ms is the default until run()
|
||||
// refines it to the actual monitor refresh rate (see detect_monitor_refresh_hz).
|
||||
UINT timer_interval_ms = 1000 / 60;
|
||||
bool timer_running = false;
|
||||
bool window_minimized = false;
|
||||
|
||||
// tracks whether we've issued at least one InvalidateRect since window
|
||||
// creation/resize. The overlay's per-frame "pixels changed" flag suppresses
|
||||
// idle blits; this flag forces the very first blit on startup or after
|
||||
// a resize so the window doesn't show garbage until the user moves the mouse.
|
||||
bool has_valid_draw_hash = false;
|
||||
|
||||
// dimensions of the configurator client area (kept in sync with WM_SIZE)
|
||||
int client_width = 0;
|
||||
int client_height = 0;
|
||||
|
||||
ConfiguratorWindow();
|
||||
~ConfiguratorWindow();
|
||||
|
||||
void run();
|
||||
|
||||
// start/stop the render timer based on visibility state
|
||||
void start_timer();
|
||||
void stop_timer();
|
||||
|
||||
static LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ namespace overlay::windows {
|
||||
el->SetAttribute("invert", entry.invert);
|
||||
el->SetAttribute("debounce_up", entry.debounce_up);
|
||||
el->SetAttribute("debounce_down", entry.debounce_down);
|
||||
el->SetAttribute("bat_threshold", entry.bat_threshold);
|
||||
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
|
||||
el->SetAttribute("modifiers", entry.modifier_mask);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
|
||||
@@ -49,6 +51,14 @@ namespace overlay::windows {
|
||||
el->QueryIntAttribute("velocity_threshold", &vel);
|
||||
entry.velocity_threshold = (unsigned short)vel;
|
||||
|
||||
int bat = 0;
|
||||
el->QueryIntAttribute("bat_threshold", &bat);
|
||||
entry.bat_threshold = bat;
|
||||
|
||||
unsigned int modifier_mask = 0;
|
||||
el->QueryUnsignedAttribute("modifiers", &modifier_mask);
|
||||
entry.modifier_mask = static_cast<uint8_t>(modifier_mask & UINT8_C(0x0F));
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -65,7 +75,7 @@ namespace overlay::windows {
|
||||
el->SetAttribute("smoothing", analog.smoothing);
|
||||
el->SetAttribute("multiplier", analog.multiplier);
|
||||
el->SetAttribute("relative", analog.relative_mode);
|
||||
el->SetAttribute("delay", analog.delay_buffer_depth);
|
||||
el->SetAttribute("delay_ms", analog.delay_ms);
|
||||
parent->InsertEndChild(el);
|
||||
}
|
||||
|
||||
@@ -88,7 +98,7 @@ namespace overlay::windows {
|
||||
el->QueryBoolAttribute("smoothing", &a.smoothing);
|
||||
el->QueryIntAttribute("multiplier", &a.multiplier);
|
||||
el->QueryBoolAttribute("relative", &a.relative_mode);
|
||||
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
|
||||
el->QueryUnsignedAttribute("delay_ms", &a.delay_ms);
|
||||
|
||||
return a;
|
||||
}
|
||||
@@ -155,6 +165,28 @@ namespace overlay::windows {
|
||||
}
|
||||
}
|
||||
|
||||
// modifier buttons
|
||||
auto *mod_el = tmpl_el->FirstChildElement("modifier_buttons");
|
||||
if (mod_el) {
|
||||
auto *btn_el = mod_el->FirstChildElement("button");
|
||||
while (btn_el) {
|
||||
const char *btn_name = btn_el->Attribute("name");
|
||||
std::string btn_name_str = btn_name ? btn_name : "";
|
||||
|
||||
TemplateButtonBinding *binding = nullptr;
|
||||
for (auto &b : tmpl.modifier_buttons) {
|
||||
if (b.name == btn_name_str) { binding = &b; break; }
|
||||
}
|
||||
if (!binding) {
|
||||
tmpl.modifier_buttons.push_back(
|
||||
{btn_name_str, read_button_entry(btn_el), {}});
|
||||
} else {
|
||||
binding->alternatives.push_back(read_button_entry(btn_el));
|
||||
}
|
||||
btn_el = btn_el->NextSiblingElement("button");
|
||||
}
|
||||
}
|
||||
|
||||
// keypad buttons
|
||||
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
|
||||
if (kp_el) {
|
||||
@@ -239,6 +271,7 @@ namespace overlay::windows {
|
||||
bool save_user_template(
|
||||
const ControllerTemplate &tmpl,
|
||||
const bool save_buttons,
|
||||
const bool save_modifiers,
|
||||
const bool save_keypads,
|
||||
const bool save_analogs,
|
||||
const bool save_lights) {
|
||||
@@ -295,6 +328,24 @@ namespace overlay::windows {
|
||||
}
|
||||
tmpl_el->InsertEndChild(buttons_el);
|
||||
|
||||
// modifier buttons - skip unbound entries
|
||||
if (!tmpl.modifier_buttons.empty()) {
|
||||
auto *mod_el = doc.NewElement("modifier_buttons");
|
||||
if (save_modifiers) {
|
||||
for (auto &btn : tmpl.modifier_buttons) {
|
||||
if (!btn.primary.is_unbound()) {
|
||||
write_button_entry(doc, mod_el, btn.name, btn.primary);
|
||||
}
|
||||
for (auto &alt : btn.alternatives) {
|
||||
if (!alt.is_unbound()) {
|
||||
write_button_entry(doc, mod_el, btn.name, alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tmpl_el->InsertEndChild(mod_el);
|
||||
}
|
||||
|
||||
// keypad buttons — skip unbound entries
|
||||
if (!tmpl.keypad_buttons.empty()) {
|
||||
auto *kp_el = doc.NewElement("keypad_buttons");
|
||||
@@ -354,13 +405,12 @@ namespace overlay::windows {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(path_tmp, path, ec);
|
||||
if (ec) {
|
||||
log_warning("templates", "failed to rename templates file: {}", ec.message());
|
||||
if (MoveFileExW(path_tmp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0) {
|
||||
log_warning("templates", "failed to rename templates file: 0x{:08x}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
log_info("templates", "templates file saved successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -392,8 +442,7 @@ namespace overlay::windows {
|
||||
auto path_tmp = path;
|
||||
path_tmp.replace_extension(L"tmp");
|
||||
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(path_tmp, path, ec);
|
||||
MoveFileExW(path_tmp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,8 +478,7 @@ namespace overlay::windows {
|
||||
auto path_tmp = path;
|
||||
path_tmp.replace_extension(L"tmp");
|
||||
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(path_tmp, path, ec);
|
||||
MoveFileExW(path_tmp.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
@@ -46,6 +46,8 @@ struct OptionDefinition {
|
||||
|
||||
// for OptionPickerType::FilePath
|
||||
std::string file_extension = "";
|
||||
|
||||
std::string quick_setting_category = "";
|
||||
};
|
||||
|
||||
class Option {
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace cfg {
|
||||
if (SCREEN_RESIZE_CFG_PATH_OVERRIDE.has_value()) {
|
||||
this->config_path = SCREEN_RESIZE_CFG_PATH_OVERRIDE.value();
|
||||
if (fileutils::file_exists(this->config_path)) {
|
||||
log_info("ScreenResize", "loading config from: {}", this->config_path.string());
|
||||
log_info("ScreenResize", "loading config from: {}", this->config_path);
|
||||
file_exists = true;
|
||||
}
|
||||
} else {
|
||||
@@ -35,7 +35,7 @@ namespace cfg {
|
||||
|
||||
ScreenResize::~ScreenResize() {
|
||||
}
|
||||
|
||||
|
||||
void ScreenResize::config_load() {
|
||||
std::string config = fileutils::text_read(this->config_path);
|
||||
if (config.empty()) {
|
||||
@@ -78,7 +78,7 @@ namespace cfg {
|
||||
eamuse_get_game(),
|
||||
use_game_setting,
|
||||
root);
|
||||
|
||||
|
||||
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
|
||||
if (this->enable_screen_resize) {
|
||||
log_misc("ScreenResize", "enabled by config file");
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# fails the build if a PE binary statically imports a forbidden DLL.
|
||||
#
|
||||
# some DLLs must never end up in spice's static import table, for two reasons:
|
||||
#
|
||||
# 1. user-overridable DLLs (e.g. DXVK's d3d9.dll): users drop their own copy
|
||||
# into the modules directory to replace the system one. a static import
|
||||
# forces the loader to load the SYSTEM copy at process startup - before the
|
||||
# modules directory is added to the DLL search path and before the game DLL
|
||||
# loads - so the user-supplied override never takes effect (see issue #779).
|
||||
#
|
||||
# 2. DLLs that break games when present (e.g. Media Foundation: mf/mfplat/
|
||||
# mfreadwrite): a static import loads them eagerly and breaks Unity games.
|
||||
#
|
||||
# in both cases the DLL must instead be loaded dynamically (libutils::try_library
|
||||
# / GetProcAddress / delay load) so it is only pulled in when actually needed.
|
||||
#
|
||||
# invoked via `cmake -P` from a POST_BUILD step. required -D variables:
|
||||
# OBJDUMP - path to objdump (CMAKE_OBJDUMP)
|
||||
# TARGET_FILE - path to the PE binary to inspect
|
||||
# FORBIDDEN - semicolon-separated list of lowercase DLL names to reject
|
||||
|
||||
if(NOT OBJDUMP OR NOT EXISTS "${OBJDUMP}")
|
||||
message(WARNING
|
||||
"check_no_static_dll_imports: objdump not found, skipping import check for ${TARGET_FILE}")
|
||||
return()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${OBJDUMP}" -p "${TARGET_FILE}"
|
||||
OUTPUT_VARIABLE dump_output
|
||||
RESULT_VARIABLE dump_result
|
||||
ERROR_VARIABLE dump_error)
|
||||
|
||||
if(NOT dump_result EQUAL 0)
|
||||
message(WARNING
|
||||
"check_no_static_dll_imports: objdump failed for ${TARGET_FILE}: ${dump_error}")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# both GNU objdump and llvm-objdump print one "DLL Name: <name>" line per
|
||||
# statically imported DLL in their PE private-header dump.
|
||||
string(REGEX MATCHALL "DLL Name:[ \t]*[^\n\r]+" dll_lines "${dump_output}")
|
||||
|
||||
set(violations "")
|
||||
foreach(line IN LISTS dll_lines)
|
||||
string(REGEX REPLACE "DLL Name:[ \t]*" "" dll_name "${line}")
|
||||
string(STRIP "${dll_name}" dll_name)
|
||||
string(TOLOWER "${dll_name}" dll_name_lower)
|
||||
if(dll_name_lower IN_LIST FORBIDDEN)
|
||||
list(APPEND violations "${dll_name}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(violations)
|
||||
list(REMOVE_DUPLICATES violations)
|
||||
string(REPLACE ";" ", " violations_str "${violations}")
|
||||
message(FATAL_ERROR
|
||||
"static DLL import check FAILED for ${TARGET_FILE}\n"
|
||||
" forbidden static imports found: ${violations_str}\n"
|
||||
"\n"
|
||||
" these DLLs must never be statically imported by spice:\n"
|
||||
" * user-overridable DLLs (e.g. DXVK d3d9.dll) - a static import loads the\n"
|
||||
" system copy at startup and preempts the modules override (issue #779).\n"
|
||||
" * Media Foundation DLLs (mf/mfplat/mfreadwrite) - a static import breaks\n"
|
||||
" Unity games.\n"
|
||||
"\n"
|
||||
" fix: load the DLL dynamically instead - replace the direct API call with a\n"
|
||||
" libutils::try_library() + libutils::try_proc() lookup (or a delay load), then\n"
|
||||
" call through the resolved function pointer.")
|
||||
endif()
|
||||
|
||||
message(STATUS "static DLL import check passed for ${TARGET_FILE}")
|
||||
+1
@@ -7,6 +7,7 @@ RUN pacman --noconfirm -Syu git \
|
||||
ninja \
|
||||
cmake \
|
||||
unzip \
|
||||
nasm \
|
||||
mingw-w64-crt \
|
||||
mingw-w64-winpthreads \
|
||||
mingw-w64-gcc \
|
||||
|
||||
+95
-95
@@ -1,95 +1,95 @@
|
||||
Copyright (c) 2017, keshikan (http://www.keshikan.net),
|
||||
with Reserved Font Name "DSEG".
|
||||
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Copyright (c) 2017, keshikan (http://www.keshikan.net),
|
||||
with Reserved Font Name "DSEG".
|
||||
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
+11023
-11023
File diff suppressed because one or more lines are too long
+552
@@ -0,0 +1,552 @@
|
||||
// This code comes from:
|
||||
// https://github.com/dhbaird/easywsclient
|
||||
//
|
||||
// To get the latest version:
|
||||
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp
|
||||
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp
|
||||
|
||||
#include "easywsclient.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment( lib, "ws2_32" )
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
typedef SOCKET socket_t;
|
||||
#ifndef _SSIZE_T_DEFINED
|
||||
typedef int ssize_t;
|
||||
#define _SSIZE_T_DEFINED
|
||||
#endif
|
||||
#ifndef _SOCKET_T_DEFINED
|
||||
typedef SOCKET socket_t;
|
||||
#define _SOCKET_T_DEFINED
|
||||
#endif
|
||||
#if defined(_MSC_VER) && !defined(snprintf)
|
||||
#define snprintf _snprintf_s
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#define socketerrno WSAGetLastError()
|
||||
#define SOCKET_EAGAIN_EINPROGRESS WSAEINPROGRESS
|
||||
#define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <errno.h>
|
||||
typedef int socket_t;
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET (-1)
|
||||
#endif
|
||||
#ifndef SOCKET_ERROR
|
||||
#define SOCKET_ERROR (-1)
|
||||
#endif
|
||||
#define closesocket(s) ::close(s)
|
||||
#include <errno.h>
|
||||
#define socketerrno errno
|
||||
#define SOCKET_EAGAIN_EINPROGRESS EAGAIN
|
||||
#define SOCKET_EWOULDBLOCK EWOULDBLOCK
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
// When false, easywsclient's diagnostics are suppressed. The host application
|
||||
// opts in by setting this flag (see obs_websocket.cpp).
|
||||
bool EASYWSCLIENT_LOGGING_ENABLED = false;
|
||||
|
||||
// Route easywsclient's diagnostic output through the project logger instead of
|
||||
// writing to stderr, without editing the upstream source lines below: these two
|
||||
// macros redirect fprintf(stderr, ...) / fputs(..., stderr) to log_misc.
|
||||
// Only emitted when EASYWSCLIENT_LOGGING_ENABLED is set.
|
||||
static inline void easywsclient_logf(const char *fmt, ...) {
|
||||
if (!EASYWSCLIENT_LOGGING_ENABLED) {
|
||||
return;
|
||||
}
|
||||
char buf[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
// trim trailing newline(s); the logger appends its own
|
||||
size_t len = strlen(buf);
|
||||
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
|
||||
buf[--len] = '\0';
|
||||
}
|
||||
log_misc("easywsclient", "{}", buf);
|
||||
}
|
||||
#define fprintf(stream, ...) easywsclient_logf(__VA_ARGS__)
|
||||
#define fputs(str, stream) easywsclient_logf("%s", str)
|
||||
|
||||
using namespace easywsclient;
|
||||
|
||||
namespace { // private module-only namespace
|
||||
|
||||
socket_t hostname_connect(const std::string& hostname, int port) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result;
|
||||
struct addrinfo *p;
|
||||
int ret;
|
||||
socket_t sockfd = INVALID_SOCKET;
|
||||
char sport[16];
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
snprintf(sport, 16, "%d", port);
|
||||
if ((ret = getaddrinfo(hostname.c_str(), sport, &hints, &result)) != 0)
|
||||
{
|
||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
|
||||
return 1;
|
||||
}
|
||||
for(p = result; p != NULL; p = p->ai_next)
|
||||
{
|
||||
sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
if (sockfd == INVALID_SOCKET) { continue; }
|
||||
if (connect(sockfd, p->ai_addr, p->ai_addrlen) != SOCKET_ERROR) {
|
||||
break;
|
||||
}
|
||||
closesocket(sockfd);
|
||||
sockfd = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
|
||||
class _DummyWebSocket : public easywsclient::WebSocket
|
||||
{
|
||||
public:
|
||||
void poll(int timeout) { }
|
||||
void send(const std::string& message) { }
|
||||
void sendBinary(const std::string& message) { }
|
||||
void sendBinary(const std::vector<uint8_t>& message) { }
|
||||
void sendPing() { }
|
||||
void close() { }
|
||||
readyStateValues getReadyState() const { return CLOSED; }
|
||||
void _dispatch(Callback_Imp & callable) { }
|
||||
void _dispatchBinary(BytesCallback_Imp& callable) { }
|
||||
};
|
||||
|
||||
|
||||
class _RealWebSocket : public easywsclient::WebSocket
|
||||
{
|
||||
public:
|
||||
// http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
// |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
// |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
// | |1|2|3| |K| | |
|
||||
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
// | Extended payload length continued, if payload len == 127 |
|
||||
// + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
// | |Masking-key, if MASK set to 1 |
|
||||
// +-------------------------------+-------------------------------+
|
||||
// | Masking-key (continued) | Payload Data |
|
||||
// +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
// : Payload Data continued ... :
|
||||
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
// | Payload Data continued ... |
|
||||
// +---------------------------------------------------------------+
|
||||
|
||||
struct wsheader_type {
|
||||
unsigned header_size;
|
||||
bool fin;
|
||||
bool mask;
|
||||
enum opcode_type {
|
||||
CONTINUATION = 0x0,
|
||||
TEXT_FRAME = 0x1,
|
||||
BINARY_FRAME = 0x2,
|
||||
CLOSE = 8,
|
||||
PING = 9,
|
||||
PONG = 0xa,
|
||||
} opcode;
|
||||
int N0;
|
||||
uint64_t N;
|
||||
uint8_t masking_key[4];
|
||||
};
|
||||
|
||||
std::vector<uint8_t> rxbuf;
|
||||
std::vector<uint8_t> txbuf;
|
||||
std::vector<uint8_t> receivedData;
|
||||
|
||||
socket_t sockfd;
|
||||
readyStateValues readyState;
|
||||
bool useMask;
|
||||
bool isRxBad;
|
||||
|
||||
_RealWebSocket(socket_t sockfd, bool useMask) : sockfd(sockfd), readyState(OPEN), useMask(useMask), isRxBad(false) {
|
||||
}
|
||||
|
||||
readyStateValues getReadyState() const {
|
||||
return readyState;
|
||||
}
|
||||
|
||||
void poll(int timeout) { // timeout in milliseconds
|
||||
if (readyState == CLOSED) {
|
||||
if (timeout > 0) {
|
||||
timeval tv = { timeout/1000, (timeout%1000) * 1000 };
|
||||
select(0, NULL, NULL, NULL, &tv);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (timeout != 0) {
|
||||
fd_set rfds;
|
||||
fd_set wfds;
|
||||
timeval tv = { timeout/1000, (timeout%1000) * 1000 };
|
||||
FD_ZERO(&rfds);
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(sockfd, &rfds);
|
||||
if (txbuf.size()) { FD_SET(sockfd, &wfds); }
|
||||
select(sockfd + 1, &rfds, &wfds, NULL, timeout > 0 ? &tv : NULL);
|
||||
}
|
||||
while (true) {
|
||||
// FD_ISSET(0, &rfds) will be true
|
||||
int N = rxbuf.size();
|
||||
ssize_t ret;
|
||||
rxbuf.resize(N + 1500);
|
||||
ret = recv(sockfd, (char*)&rxbuf[0] + N, 1500, 0);
|
||||
if (false) { }
|
||||
else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
|
||||
rxbuf.resize(N);
|
||||
break;
|
||||
}
|
||||
else if (ret <= 0) {
|
||||
rxbuf.resize(N);
|
||||
closesocket(sockfd);
|
||||
readyState = CLOSED;
|
||||
fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
rxbuf.resize(N + ret);
|
||||
}
|
||||
}
|
||||
while (txbuf.size()) {
|
||||
int ret = ::send(sockfd, (char*)&txbuf[0], txbuf.size(), 0);
|
||||
if (false) { } // ??
|
||||
else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) {
|
||||
break;
|
||||
}
|
||||
else if (ret <= 0) {
|
||||
closesocket(sockfd);
|
||||
readyState = CLOSED;
|
||||
fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
txbuf.erase(txbuf.begin(), txbuf.begin() + ret);
|
||||
}
|
||||
}
|
||||
if (!txbuf.size() && readyState == CLOSING) {
|
||||
closesocket(sockfd);
|
||||
readyState = CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void _dispatch(Callback_Imp & callable) {
|
||||
struct CallbackAdapter : public BytesCallback_Imp
|
||||
// Adapt void(const std::string<uint8_t>&) to void(const std::string&)
|
||||
{
|
||||
Callback_Imp& callable;
|
||||
CallbackAdapter(Callback_Imp& callable) : callable(callable) { }
|
||||
void operator()(const std::vector<uint8_t>& message) {
|
||||
std::string stringMessage(message.begin(), message.end());
|
||||
callable(stringMessage);
|
||||
}
|
||||
};
|
||||
CallbackAdapter bytesCallback(callable);
|
||||
_dispatchBinary(bytesCallback);
|
||||
}
|
||||
|
||||
virtual void _dispatchBinary(BytesCallback_Imp& callable) {
|
||||
// TODO: consider acquiring a lock on rxbuf...
|
||||
while (true) {
|
||||
wsheader_type ws;
|
||||
if (rxbuf.size() < 2) { return; /* Need at least 2 */ }
|
||||
const uint8_t * data = (uint8_t *) &rxbuf[0]; // peek, but don't consume
|
||||
ws.fin = (data[0] & 0x80) == 0x80;
|
||||
ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f);
|
||||
ws.mask = (data[1] & 0x80) == 0x80;
|
||||
ws.N0 = (data[1] & 0x7f);
|
||||
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
|
||||
if (rxbuf.size() < ws.header_size) { return; /* Need: ws.header_size - rxbuf.size() */ }
|
||||
int i = 0;
|
||||
if (ws.N0 < 126) {
|
||||
ws.N = ws.N0;
|
||||
i = 2;
|
||||
}
|
||||
else if (ws.N0 == 126) {
|
||||
ws.N = 0;
|
||||
ws.N |= ((uint64_t) data[2]) << 8;
|
||||
ws.N |= ((uint64_t) data[3]) << 0;
|
||||
i = 4;
|
||||
}
|
||||
else if (ws.N0 == 127) {
|
||||
ws.N = 0;
|
||||
ws.N |= ((uint64_t) data[2]) << 56;
|
||||
ws.N |= ((uint64_t) data[3]) << 48;
|
||||
ws.N |= ((uint64_t) data[4]) << 40;
|
||||
ws.N |= ((uint64_t) data[5]) << 32;
|
||||
ws.N |= ((uint64_t) data[6]) << 24;
|
||||
ws.N |= ((uint64_t) data[7]) << 16;
|
||||
ws.N |= ((uint64_t) data[8]) << 8;
|
||||
ws.N |= ((uint64_t) data[9]) << 0;
|
||||
i = 10;
|
||||
}
|
||||
if (ws.mask) {
|
||||
ws.masking_key[0] = ((uint8_t) data[i+0]) << 0;
|
||||
ws.masking_key[1] = ((uint8_t) data[i+1]) << 0;
|
||||
ws.masking_key[2] = ((uint8_t) data[i+2]) << 0;
|
||||
ws.masking_key[3] = ((uint8_t) data[i+3]) << 0;
|
||||
}
|
||||
else {
|
||||
ws.masking_key[0] = 0;
|
||||
ws.masking_key[1] = 0;
|
||||
ws.masking_key[2] = 0;
|
||||
ws.masking_key[3] = 0;
|
||||
}
|
||||
|
||||
// Note: The checks above should hopefully ensure this addition
|
||||
// cannot overflow:
|
||||
if (rxbuf.size() < ws.header_size+ws.N) { return; /* Need: ws.header_size+ws.N - rxbuf.size() */ }
|
||||
|
||||
// We got a whole message, now do something with it:
|
||||
if (false) { }
|
||||
else if (
|
||||
ws.opcode == wsheader_type::TEXT_FRAME
|
||||
|| ws.opcode == wsheader_type::BINARY_FRAME
|
||||
|| ws.opcode == wsheader_type::CONTINUATION
|
||||
) {
|
||||
if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } }
|
||||
receivedData.insert(receivedData.end(), rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed
|
||||
if (ws.fin) {
|
||||
callable((const std::vector<uint8_t>) receivedData);
|
||||
receivedData.erase(receivedData.begin(), receivedData.end());
|
||||
std::vector<uint8_t> ().swap(receivedData);// free memory
|
||||
}
|
||||
}
|
||||
else if (ws.opcode == wsheader_type::PING) {
|
||||
if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } }
|
||||
std::string data(rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);
|
||||
sendData(wsheader_type::PONG, data.size(), data.begin(), data.end());
|
||||
}
|
||||
else if (ws.opcode == wsheader_type::PONG) { }
|
||||
else if (ws.opcode == wsheader_type::CLOSE) { close(); }
|
||||
else { fprintf(stderr, "ERROR: Got unexpected WebSocket message.\n"); close(); }
|
||||
|
||||
rxbuf.erase(rxbuf.begin(), rxbuf.begin() + ws.header_size+(size_t)ws.N);
|
||||
}
|
||||
}
|
||||
|
||||
void sendPing() {
|
||||
std::string empty;
|
||||
sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end());
|
||||
}
|
||||
|
||||
void send(const std::string& message) {
|
||||
sendData(wsheader_type::TEXT_FRAME, message.size(), message.begin(), message.end());
|
||||
}
|
||||
|
||||
void sendBinary(const std::string& message) {
|
||||
sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end());
|
||||
}
|
||||
|
||||
void sendBinary(const std::vector<uint8_t>& message) {
|
||||
sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end());
|
||||
}
|
||||
|
||||
template<class Iterator>
|
||||
void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end) {
|
||||
// TODO:
|
||||
// Masking key should (must) be derived from a high quality random
|
||||
// number generator, to mitigate attacks on non-WebSocket friendly
|
||||
// middleware:
|
||||
const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 };
|
||||
// TODO: consider acquiring a lock on txbuf...
|
||||
if (readyState == CLOSING || readyState == CLOSED) { return; }
|
||||
std::vector<uint8_t> header;
|
||||
header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0), 0);
|
||||
header[0] = 0x80 | type;
|
||||
if (false) { }
|
||||
else if (message_size < 126) {
|
||||
header[1] = (message_size & 0xff) | (useMask ? 0x80 : 0);
|
||||
if (useMask) {
|
||||
header[2] = masking_key[0];
|
||||
header[3] = masking_key[1];
|
||||
header[4] = masking_key[2];
|
||||
header[5] = masking_key[3];
|
||||
}
|
||||
}
|
||||
else if (message_size < 65536) {
|
||||
header[1] = 126 | (useMask ? 0x80 : 0);
|
||||
header[2] = (message_size >> 8) & 0xff;
|
||||
header[3] = (message_size >> 0) & 0xff;
|
||||
if (useMask) {
|
||||
header[4] = masking_key[0];
|
||||
header[5] = masking_key[1];
|
||||
header[6] = masking_key[2];
|
||||
header[7] = masking_key[3];
|
||||
}
|
||||
}
|
||||
else { // TODO: run coverage testing here
|
||||
header[1] = 127 | (useMask ? 0x80 : 0);
|
||||
header[2] = (message_size >> 56) & 0xff;
|
||||
header[3] = (message_size >> 48) & 0xff;
|
||||
header[4] = (message_size >> 40) & 0xff;
|
||||
header[5] = (message_size >> 32) & 0xff;
|
||||
header[6] = (message_size >> 24) & 0xff;
|
||||
header[7] = (message_size >> 16) & 0xff;
|
||||
header[8] = (message_size >> 8) & 0xff;
|
||||
header[9] = (message_size >> 0) & 0xff;
|
||||
if (useMask) {
|
||||
header[10] = masking_key[0];
|
||||
header[11] = masking_key[1];
|
||||
header[12] = masking_key[2];
|
||||
header[13] = masking_key[3];
|
||||
}
|
||||
}
|
||||
// N.B. - txbuf will keep growing until it can be transmitted over the socket:
|
||||
txbuf.insert(txbuf.end(), header.begin(), header.end());
|
||||
txbuf.insert(txbuf.end(), message_begin, message_end);
|
||||
if (useMask) {
|
||||
size_t message_offset = txbuf.size() - message_size;
|
||||
for (size_t i = 0; i != message_size; ++i) {
|
||||
txbuf[message_offset + i] ^= masking_key[i&0x3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void close() {
|
||||
if(readyState == CLOSING || readyState == CLOSED) { return; }
|
||||
readyState = CLOSING;
|
||||
uint8_t closeFrame[6] = {0x88, 0x80, 0x00, 0x00, 0x00, 0x00}; // last 4 bytes are a masking key
|
||||
std::vector<uint8_t> header(closeFrame, closeFrame+6);
|
||||
txbuf.insert(txbuf.end(), header.begin(), header.end());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
easywsclient::WebSocket::pointer from_url(const std::string& url, bool useMask, const std::string& origin) {
|
||||
char host[512];
|
||||
int port;
|
||||
char path[512];
|
||||
if (url.size() >= 512) {
|
||||
fprintf(stderr, "ERROR: url size limit exceeded: %s\n", url.c_str());
|
||||
return NULL;
|
||||
}
|
||||
if (origin.size() >= 200) {
|
||||
fprintf(stderr, "ERROR: origin size limit exceeded: %s\n", origin.c_str());
|
||||
return NULL;
|
||||
}
|
||||
if (false) { }
|
||||
else if (sscanf(url.c_str(), "ws://%[^:/]:%d/%s", host, &port, path) == 3) {
|
||||
}
|
||||
else if (sscanf(url.c_str(), "ws://%[^:/]/%s", host, path) == 2) {
|
||||
port = 80;
|
||||
}
|
||||
else if (sscanf(url.c_str(), "ws://%[^:/]:%d", host, &port) == 2) {
|
||||
path[0] = '\0';
|
||||
}
|
||||
else if (sscanf(url.c_str(), "ws://%[^:/]", host) == 1) {
|
||||
port = 80;
|
||||
path[0] = '\0';
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "ERROR: Could not parse WebSocket url: %s\n", url.c_str());
|
||||
return NULL;
|
||||
}
|
||||
//fprintf(stderr, "easywsclient: connecting: host=%s port=%d path=/%s\n", host, port, path);
|
||||
socket_t sockfd = hostname_connect(host, port);
|
||||
if (sockfd == INVALID_SOCKET) {
|
||||
fprintf(stderr, "Unable to connect to %s:%d\n", host, port);
|
||||
return NULL;
|
||||
}
|
||||
{
|
||||
// XXX: this should be done non-blocking,
|
||||
char line[1024];
|
||||
int status;
|
||||
int i;
|
||||
snprintf(line, 1024, "GET /%s HTTP/1.1\r\n", path); ::send(sockfd, line, strlen(line), 0);
|
||||
if (port == 80) {
|
||||
snprintf(line, 1024, "Host: %s\r\n", host); ::send(sockfd, line, strlen(line), 0);
|
||||
}
|
||||
else {
|
||||
snprintf(line, 1024, "Host: %s:%d\r\n", host, port); ::send(sockfd, line, strlen(line), 0);
|
||||
}
|
||||
snprintf(line, 1024, "Upgrade: websocket\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||
snprintf(line, 1024, "Connection: Upgrade\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||
if (!origin.empty()) {
|
||||
snprintf(line, 1024, "Origin: %s\r\n", origin.c_str()); ::send(sockfd, line, strlen(line), 0);
|
||||
}
|
||||
snprintf(line, 1024, "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||
snprintf(line, 1024, "Sec-WebSocket-Version: 13\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||
snprintf(line, 1024, "\r\n"); ::send(sockfd, line, strlen(line), 0);
|
||||
for (i = 0; i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } }
|
||||
line[i] = 0;
|
||||
if (i == 1023) { fprintf(stderr, "ERROR: Got invalid status line connecting to: %s\n", url.c_str()); return NULL; }
|
||||
if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { fprintf(stderr, "ERROR: Got bad status connecting to %s: %s", url.c_str(), line); return NULL; }
|
||||
// TODO: verify response headers,
|
||||
while (true) {
|
||||
for (i = 0; i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } }
|
||||
if (line[0] == '\r' && line[1] == '\n') { break; }
|
||||
}
|
||||
}
|
||||
int flag = 1;
|
||||
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm
|
||||
#ifdef _WIN32
|
||||
u_long on = 1;
|
||||
ioctlsocket(sockfd, FIONBIO, &on);
|
||||
#else
|
||||
fcntl(sockfd, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
//fprintf(stderr, "Connected to: %s\n", url.c_str());
|
||||
return easywsclient::WebSocket::pointer(new _RealWebSocket(sockfd, useMask));
|
||||
}
|
||||
|
||||
} // end of module-only namespace
|
||||
|
||||
|
||||
|
||||
namespace easywsclient {
|
||||
|
||||
WebSocket::pointer WebSocket::create_dummy() {
|
||||
static pointer dummy = pointer(new _DummyWebSocket);
|
||||
return dummy;
|
||||
}
|
||||
|
||||
|
||||
WebSocket::pointer WebSocket::from_url(const std::string& url, const std::string& origin) {
|
||||
return ::from_url(url, true, origin);
|
||||
}
|
||||
|
||||
WebSocket::pointer WebSocket::from_url_no_mask(const std::string& url, const std::string& origin) {
|
||||
return ::from_url(url, false, origin);
|
||||
}
|
||||
|
||||
|
||||
} // namespace easywsclient
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD
|
||||
#define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD
|
||||
|
||||
// This code comes from:
|
||||
// https://github.com/dhbaird/easywsclient
|
||||
//
|
||||
// To get the latest version:
|
||||
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp
|
||||
// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace easywsclient {
|
||||
|
||||
struct Callback_Imp { virtual void operator()(const std::string& message) = 0; };
|
||||
struct BytesCallback_Imp { virtual void operator()(const std::vector<uint8_t>& message) = 0; };
|
||||
|
||||
class WebSocket {
|
||||
public:
|
||||
typedef WebSocket * pointer;
|
||||
typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues;
|
||||
|
||||
// Factories:
|
||||
static pointer create_dummy();
|
||||
static pointer from_url(const std::string& url, const std::string& origin = std::string());
|
||||
static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string());
|
||||
|
||||
// Interfaces:
|
||||
virtual ~WebSocket() { }
|
||||
virtual void poll(int timeout = 0) = 0; // timeout in milliseconds
|
||||
virtual void send(const std::string& message) = 0;
|
||||
virtual void sendBinary(const std::string& message) = 0;
|
||||
virtual void sendBinary(const std::vector<uint8_t>& message) = 0;
|
||||
virtual void sendPing() = 0;
|
||||
virtual void close() = 0;
|
||||
virtual readyStateValues getReadyState() const = 0;
|
||||
|
||||
template<class Callable>
|
||||
void dispatch(Callable callable)
|
||||
// For callbacks that accept a string argument.
|
||||
{ // N.B. this is compatible with both C++11 lambdas, functors and C function pointers
|
||||
struct _Callback : public Callback_Imp {
|
||||
Callable& callable;
|
||||
_Callback(Callable& callable) : callable(callable) { }
|
||||
void operator()(const std::string& message) { callable(message); }
|
||||
};
|
||||
_Callback callback(callable);
|
||||
_dispatch(callback);
|
||||
}
|
||||
|
||||
template<class Callable>
|
||||
void dispatchBinary(Callable callable)
|
||||
// For callbacks that accept a std::vector<uint8_t> argument.
|
||||
{ // N.B. this is compatible with both C++11 lambdas, functors and C function pointers
|
||||
struct _Callback : public BytesCallback_Imp {
|
||||
Callable& callable;
|
||||
_Callback(Callable& callable) : callable(callable) { }
|
||||
void operator()(const std::vector<uint8_t>& message) { callable(message); }
|
||||
};
|
||||
_Callback callback(callable);
|
||||
_dispatchBinary(callback);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void _dispatch(Callback_Imp& callable) = 0;
|
||||
virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0;
|
||||
};
|
||||
|
||||
} // namespace easywsclient
|
||||
|
||||
#endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */
|
||||
Vendored
+3222
File diff suppressed because it is too large
Load Diff
Vendored
+122
@@ -0,0 +1,122 @@
|
||||
// fpng.h - unlicense (see end of fpng.cpp)
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#ifndef FPNG_TRAIN_HUFFMAN_TABLES
|
||||
// Set to 1 when using the -t (training) option in fpng_test to generate new opaque/alpha Huffman tables for the single pass encoder.
|
||||
#define FPNG_TRAIN_HUFFMAN_TABLES (0)
|
||||
#endif
|
||||
|
||||
namespace fpng
|
||||
{
|
||||
// ---- Library initialization - call once to identify if the processor supports SSE.
|
||||
// Otherwise you'll only get scalar fallbacks.
|
||||
void fpng_init();
|
||||
|
||||
// ---- Useful Utilities
|
||||
|
||||
// Returns true if the CPU supports SSE 4.1, and SSE support wasn't disabled by setting FPNG_NO_SSE=1.
|
||||
// fpng_init() must have been called first, or it'll assert and return false.
|
||||
bool fpng_cpu_supports_sse41();
|
||||
|
||||
// Fast CRC-32 SSE4.1+pclmul or a scalar fallback (slice by 4)
|
||||
const uint32_t FPNG_CRC32_INIT = 0;
|
||||
uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32 = FPNG_CRC32_INIT);
|
||||
|
||||
// Fast Adler32 SSE4.1 Adler-32 with a scalar fallback.
|
||||
const uint32_t FPNG_ADLER32_INIT = 1;
|
||||
uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler = FPNG_ADLER32_INIT);
|
||||
|
||||
// ---- Compression
|
||||
enum
|
||||
{
|
||||
// Enables computing custom Huffman tables for each file, instead of using the custom global tables.
|
||||
// Results in roughly 6% smaller files on average, but compression is around 40% slower.
|
||||
FPNG_ENCODE_SLOWER = 1,
|
||||
|
||||
// Only use raw Deflate blocks (no compression at all). Intended for testing.
|
||||
FPNG_FORCE_UNCOMPRESSED = 2,
|
||||
};
|
||||
|
||||
// Fast PNG encoding. The resulting file can be decoded either using a standard PNG decoder or by the fpng_decode_memory() function below.
|
||||
// pImage: pointer to RGB or RGBA image pixels, R first in memory, B/A last.
|
||||
// w/h - image dimensions. Image's row pitch in bytes must is w*num_chans.
|
||||
// num_chans must be 3 or 4.
|
||||
bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector<uint8_t>& out_buf, uint32_t flags = 0);
|
||||
|
||||
#ifndef FPNG_NO_STDIO
|
||||
// Fast PNG encoding to the specified file.
|
||||
bool fpng_encode_image_to_file(const char* pFilename, const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, uint32_t flags = 0);
|
||||
#endif
|
||||
|
||||
// ---- Decompression
|
||||
|
||||
enum
|
||||
{
|
||||
FPNG_DECODE_SUCCESS = 0, // file is a valid PNG file and written by FPNG and the decode succeeded
|
||||
|
||||
FPNG_DECODE_NOT_FPNG, // file is a valid PNG file, but it wasn't written by FPNG so you should try decoding it with a general purpose PNG decoder
|
||||
|
||||
FPNG_DECODE_INVALID_ARG, // invalid function parameter
|
||||
|
||||
FPNG_DECODE_FAILED_NOT_PNG, // file cannot be a PNG file
|
||||
FPNG_DECODE_FAILED_HEADER_CRC32, // a chunk CRC32 check failed, file is likely corrupted or not PNG
|
||||
FPNG_DECODE_FAILED_INVALID_DIMENSIONS, // invalid image dimensions in IHDR chunk (0 or too large)
|
||||
FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE, // decoding the file fully into memory would likely require too much memory (only on 32bpp builds)
|
||||
FPNG_DECODE_FAILED_CHUNK_PARSING, // failed while parsing the chunk headers, or file is corrupted
|
||||
FPNG_DECODE_FAILED_INVALID_IDAT, // IDAT data length is too small and cannot be valid, file is either corrupted or it's a bug
|
||||
|
||||
// fpng_decode_file() specific errors
|
||||
FPNG_DECODE_FILE_OPEN_FAILED,
|
||||
FPNG_DECODE_FILE_TOO_LARGE,
|
||||
FPNG_DECODE_FILE_READ_FAILED,
|
||||
FPNG_DECODE_FILE_SEEK_FAILED
|
||||
};
|
||||
|
||||
// Fast PNG decoding of files ONLY created by fpng_encode_image_to_memory() or fpng_encode_image_to_file().
|
||||
// If fpng_get_info() or fpng_decode_memory() returns FPNG_DECODE_NOT_FPNG, you should decode the PNG by falling back to a general purpose decoder.
|
||||
//
|
||||
// fpng_get_info() parses the PNG header and iterates through all chunks to determine if it's a file written by FPNG, but does not decompress the actual image data so it's relatively fast.
|
||||
//
|
||||
// pImage, image_size: Pointer to PNG image data and its size
|
||||
// width, height: output image's dimensions
|
||||
// channels_in_file: will be 3 or 4
|
||||
//
|
||||
// Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
|
||||
// If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
|
||||
// If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
|
||||
int fpng_get_info(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file);
|
||||
|
||||
// fpng_decode_memory() decompresses 24/32bpp PNG files ONLY encoded by this module.
|
||||
// If the image was written by FPNG, it will decompress the image data, otherwise it will return FPNG_DECODE_NOT_FPNG in which case you should fall back to a general purpose PNG decoder (lodepng, stb_image, libpng, etc.)
|
||||
//
|
||||
// pImage, image_size: Pointer to PNG image data and its size
|
||||
// out: Output 24/32bpp image buffer
|
||||
// width, height: output image's dimensions
|
||||
// channels_in_file: will be 3 or 4
|
||||
// desired_channels: must be 3 or 4
|
||||
//
|
||||
// If the image is 24bpp and 32bpp is requested, the alpha values will be set to 0xFF.
|
||||
// If the image is 32bpp and 24bpp is requested, the alpha values will be discarded.
|
||||
//
|
||||
// Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
|
||||
// If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
|
||||
// If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
|
||||
int fpng_decode_memory(const void* pImage, uint32_t image_size, std::vector<uint8_t>& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);
|
||||
|
||||
#ifndef FPNG_NO_STDIO
|
||||
int fpng_decode_file(const char* pFilename, std::vector<uint8_t>& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);
|
||||
#endif
|
||||
|
||||
// ---- Internal API used for Huffman table training purposes
|
||||
|
||||
#if FPNG_TRAIN_HUFFMAN_TABLES
|
||||
const uint32_t HUFF_COUNTS_SIZE = 288;
|
||||
extern uint64_t g_huff_counts[HUFF_COUNTS_SIZE];
|
||||
bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector<uint8_t>& prefix, uint64_t& bit_buf, int& bit_buf_size, uint32_t *pCodes, uint8_t *pCodesizes);
|
||||
#endif
|
||||
|
||||
} // namespace fpng
|
||||
Vendored
+1
@@ -20,6 +20,7 @@ Usage:
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
+6
@@ -21,5 +21,11 @@ set(IMGUI_SOURCES
|
||||
misc/cpp/imgui_stdlib.cpp
|
||||
)
|
||||
|
||||
# spice2x: DX11 backend is only built for non-XP toolchains
|
||||
if(NOT SPICE_XP)
|
||||
list(APPEND IMGUI_HEADERS backends/imgui_impl_dx11.h)
|
||||
list(APPEND IMGUI_SOURCES backends/imgui_impl_dx11.cpp)
|
||||
endif()
|
||||
|
||||
add_library(imgui STATIC ${IMGUI_HEADERS} ${IMGUI_SOURCES})
|
||||
target_include_directories(imgui PRIVATE ${PROJECT_SOURCE_DIR})
|
||||
|
||||
@@ -0,0 +1,894 @@
|
||||
// dear imgui: Renderer Backend for DirectX11
|
||||
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
|
||||
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// Learn about Dear ImGui:
|
||||
// - FAQ https://dearimgui.com/faq
|
||||
// - Getting Started https://dearimgui.com/getting-started
|
||||
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
|
||||
// - Introduction, links and more at the top of imgui.cpp
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
|
||||
// 2026-04-23: DirectX11: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplDX11_RenderState. (#9378)
|
||||
// 2026-01-19: DirectX11: Added 'SamplerNearest' in ImGui_ImplDX11_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'.
|
||||
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
|
||||
// 2025-06-11: DirectX11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
|
||||
// 2025-05-07: DirectX11: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows).
|
||||
// 2025-02-24: [Docking] Added undocumented ImGui_ImplDX11_SetSwapChainDescs() to configure swap chain creation for secondary viewports.
|
||||
// 2025-01-06: DirectX11: Expose VertexConstantBuffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handler.
|
||||
// 2024-10-07: DirectX11: Changed default texture sampler to Clamp instead of Repeat/Wrap.
|
||||
// 2024-10-07: DirectX11: Expose selected render state in ImGui_ImplDX11_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
|
||||
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
|
||||
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
|
||||
// 2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
|
||||
// 2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer.
|
||||
// 2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled).
|
||||
// 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX11_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
|
||||
// 2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
|
||||
// 2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
|
||||
// 2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
|
||||
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
|
||||
// 2018-08-01: DirectX11: Querying for IDXGIFactory instead of IDXGIFactory1 to increase compatibility.
|
||||
// 2018-07-13: DirectX11: Fixed unreleased resources in Init and Shutdown functions.
|
||||
// 2018-06-08: Misc: Extracted imgui_impl_dx11.cpp/.h away from the old combined DX11+Win32 example.
|
||||
// 2018-06-08: DirectX11: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
|
||||
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_RenderDrawData() in the .h file so you can call it yourself.
|
||||
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
|
||||
// 2016-05-07: DirectX11: Disabling depth-write.
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DISABLE
|
||||
#include "imgui_impl_dx11.h"
|
||||
|
||||
// DirectX
|
||||
#include <stdio.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
// #ifdef _MSC_VER
|
||||
// #pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
|
||||
// #endif
|
||||
|
||||
// spice2x: resolve D3DCompile at runtime rather than static-linking d3dcompiler,
|
||||
// which would hardwire an import on d3dcompiler_47.dll (absent on stock Win7).
|
||||
static pD3DCompile ImGui_ImplDX11_GetD3DCompile()
|
||||
{
|
||||
static pD3DCompile fn = []() -> pD3DCompile {
|
||||
static const char *dlls[] = {
|
||||
"d3dcompiler_47.dll",
|
||||
"d3dcompiler_46.dll",
|
||||
"d3dcompiler_43.dll" };
|
||||
|
||||
for (const char *dll : dlls) {
|
||||
HMODULE mod = GetModuleHandleA(dll);
|
||||
if (!mod) {
|
||||
mod = LoadLibraryA(dll);
|
||||
}
|
||||
if (mod) {
|
||||
if (auto p = (pD3DCompile) GetProcAddress(mod, "D3DCompile")) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
return fn;
|
||||
}
|
||||
|
||||
// Clang/GCC warnings with -Weverything
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
|
||||
#endif
|
||||
|
||||
// DirectX11 data
|
||||
struct ImGui_ImplDX11_Texture
|
||||
{
|
||||
ID3D11Texture2D* pTexture;
|
||||
ID3D11ShaderResourceView* pTextureView;
|
||||
};
|
||||
|
||||
struct ImGui_ImplDX11_Data
|
||||
{
|
||||
ID3D11Device* pd3dDevice;
|
||||
ID3D11DeviceContext* pd3dDeviceContext;
|
||||
IDXGIFactory* pFactory;
|
||||
ID3D11Buffer* pVB;
|
||||
ID3D11Buffer* pIB;
|
||||
ID3D11VertexShader* pVertexShader;
|
||||
ID3D11InputLayout* pInputLayout;
|
||||
ID3D11Buffer* pVertexConstantBuffer;
|
||||
ID3D11PixelShader* pPixelShader;
|
||||
ID3D11SamplerState* pTexSamplerLinear;
|
||||
ID3D11SamplerState* pTexSamplerNearest;
|
||||
ID3D11RasterizerState* pRasterizerState;
|
||||
ID3D11BlendState* pBlendState;
|
||||
ID3D11DepthStencilState* pDepthStencilState;
|
||||
int VertexBufferSize;
|
||||
int IndexBufferSize;
|
||||
ImGui_ImplDX11_RenderState* RenderState; // == (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState during rendering.
|
||||
ImVector<DXGI_SWAP_CHAIN_DESC> SwapChainDescsForViewports;
|
||||
|
||||
ImGui_ImplDX11_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
|
||||
};
|
||||
|
||||
struct VERTEX_CONSTANT_BUFFER_DX11
|
||||
{
|
||||
float mvp[4][4];
|
||||
};
|
||||
|
||||
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
|
||||
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
|
||||
static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData()
|
||||
{
|
||||
return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
|
||||
}
|
||||
|
||||
// Forward Declarations
|
||||
static void ImGui_ImplDX11_InitMultiViewportSupport();
|
||||
static void ImGui_ImplDX11_ShutdownMultiViewportSupport();
|
||||
|
||||
// Functions
|
||||
static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11DeviceContext* device_ctx)
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
|
||||
// Setup viewport
|
||||
D3D11_VIEWPORT vp = {};
|
||||
vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x;
|
||||
vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y;
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
vp.TopLeftX = vp.TopLeftY = 0;
|
||||
device_ctx->RSSetViewports(1, &vp);
|
||||
|
||||
// Setup orthographic projection matrix into our constant buffer
|
||||
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
|
||||
D3D11_MAPPED_SUBRESOURCE mapped_resource;
|
||||
if (device_ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) == S_OK)
|
||||
{
|
||||
VERTEX_CONSTANT_BUFFER_DX11* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX11*)mapped_resource.pData;
|
||||
float L = draw_data->DisplayPos.x;
|
||||
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
|
||||
float T = draw_data->DisplayPos.y;
|
||||
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
|
||||
float mvp[4][4] =
|
||||
{
|
||||
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
|
||||
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
|
||||
{ 0.0f, 0.0f, 0.5f, 0.0f },
|
||||
{ (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f },
|
||||
};
|
||||
memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
|
||||
device_ctx->Unmap(bd->pVertexConstantBuffer, 0);
|
||||
}
|
||||
|
||||
// Setup shader and vertex buffers
|
||||
unsigned int stride = sizeof(ImDrawVert);
|
||||
unsigned int offset = 0;
|
||||
device_ctx->IASetInputLayout(bd->pInputLayout);
|
||||
device_ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
|
||||
device_ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
|
||||
device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
device_ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
|
||||
device_ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
|
||||
device_ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
|
||||
device_ctx->PSSetSamplers(0, 1, &bd->pTexSamplerLinear);
|
||||
device_ctx->GSSetShader(nullptr, nullptr, 0);
|
||||
device_ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
device_ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
device_ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
|
||||
|
||||
// Setup render state
|
||||
const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
|
||||
device_ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
|
||||
device_ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
|
||||
device_ctx->RSSetState(bd->pRasterizerState);
|
||||
}
|
||||
|
||||
// Draw callbacks
|
||||
static void ImGui_ImplDX11_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way.
|
||||
static void ImGui_ImplDX11_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); bd->RenderState->DeviceContext->PSSetSamplers(0, 1, &bd->pTexSamplerLinear); }
|
||||
static void ImGui_ImplDX11_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); bd->RenderState->DeviceContext->PSSetSamplers(0, 1, &bd->pTexSamplerNearest); }
|
||||
|
||||
// Render function
|
||||
void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
|
||||
{
|
||||
// Avoid rendering when minimized
|
||||
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
|
||||
return;
|
||||
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
ID3D11DeviceContext* device = bd->pd3dDeviceContext;
|
||||
|
||||
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
|
||||
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
|
||||
if (draw_data->Textures != nullptr)
|
||||
for (ImTextureData* tex : *draw_data->Textures)
|
||||
if (tex->Status != ImTextureStatus_OK)
|
||||
ImGui_ImplDX11_UpdateTexture(tex);
|
||||
|
||||
// Create and grow vertex/index buffers if needed
|
||||
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
|
||||
{
|
||||
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
|
||||
D3D11_BUFFER_DESC desc = {};
|
||||
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
desc.ByteWidth = (UINT)bd->VertexBufferSize * sizeof(ImDrawVert);
|
||||
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
desc.MiscFlags = 0;
|
||||
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
|
||||
return;
|
||||
}
|
||||
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
|
||||
{
|
||||
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
|
||||
D3D11_BUFFER_DESC desc = {};
|
||||
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
desc.ByteWidth = (UINT)bd->IndexBufferSize * sizeof(ImDrawIdx);
|
||||
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload vertex/index data into a single contiguous GPU buffer
|
||||
D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
|
||||
if (device->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
|
||||
return;
|
||||
if (device->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
|
||||
return;
|
||||
ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
|
||||
ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
|
||||
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
||||
{
|
||||
memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
vtx_dst += draw_list->VtxBuffer.Size;
|
||||
idx_dst += draw_list->IdxBuffer.Size;
|
||||
}
|
||||
device->Unmap(bd->pVB, 0);
|
||||
device->Unmap(bd->pIB, 0);
|
||||
|
||||
// Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
|
||||
struct BACKUP_DX11_STATE
|
||||
{
|
||||
UINT ScissorRectsCount, ViewportsCount;
|
||||
D3D11_RECT ScissorRects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
|
||||
D3D11_VIEWPORT Viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];
|
||||
ID3D11RasterizerState* RS;
|
||||
ID3D11BlendState* BlendState;
|
||||
FLOAT BlendFactor[4];
|
||||
UINT SampleMask;
|
||||
UINT StencilRef;
|
||||
ID3D11DepthStencilState* DepthStencilState;
|
||||
ID3D11ShaderResourceView* PSShaderResource;
|
||||
ID3D11SamplerState* PSSampler;
|
||||
ID3D11PixelShader* PS;
|
||||
ID3D11VertexShader* VS;
|
||||
ID3D11GeometryShader* GS;
|
||||
UINT PSInstancesCount, VSInstancesCount, GSInstancesCount;
|
||||
ID3D11ClassInstance *PSInstances[256], *VSInstances[256], *GSInstances[256]; // 256 is max according to PSSetShader documentation
|
||||
D3D11_PRIMITIVE_TOPOLOGY PrimitiveTopology;
|
||||
ID3D11Buffer* IndexBuffer, *VertexBuffer, *VSConstantBuffer;
|
||||
UINT IndexBufferOffset, VertexBufferStride, VertexBufferOffset;
|
||||
DXGI_FORMAT IndexBufferFormat;
|
||||
ID3D11InputLayout* InputLayout;
|
||||
};
|
||||
BACKUP_DX11_STATE old = {};
|
||||
old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
|
||||
device->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
|
||||
device->RSGetViewports(&old.ViewportsCount, old.Viewports);
|
||||
device->RSGetState(&old.RS);
|
||||
device->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
|
||||
device->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
|
||||
device->PSGetShaderResources(0, 1, &old.PSShaderResource);
|
||||
device->PSGetSamplers(0, 1, &old.PSSampler);
|
||||
old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256;
|
||||
device->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
|
||||
device->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
|
||||
device->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
|
||||
device->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
|
||||
|
||||
device->IAGetPrimitiveTopology(&old.PrimitiveTopology);
|
||||
device->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
|
||||
device->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
|
||||
device->IAGetInputLayout(&old.InputLayout);
|
||||
|
||||
// Setup desired DX state
|
||||
ImGui_ImplDX11_SetupRenderState(draw_data, device);
|
||||
|
||||
// Setup render state structure (for callbacks and custom texture bindings)
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
ImGui_ImplDX11_RenderState render_state;
|
||||
render_state.Device = bd->pd3dDevice;
|
||||
render_state.DeviceContext = bd->pd3dDeviceContext;
|
||||
render_state.VertexConstantBuffer = bd->pVertexConstantBuffer;
|
||||
platform_io.Renderer_RenderState = bd->RenderState = &render_state;
|
||||
|
||||
// Render command lists
|
||||
// (Because we merged all buffers into a single one, we maintain our own offset into them)
|
||||
int global_idx_offset = 0;
|
||||
int global_vtx_offset = 0;
|
||||
ImVec2 clip_off = draw_data->DisplayPos;
|
||||
ImVec2 clip_scale = draw_data->FramebufferScale;
|
||||
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
||||
{
|
||||
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
|
||||
if (pcmd->UserCallback != nullptr)
|
||||
{
|
||||
// User callback, registered via ImDrawList::AddCallback()
|
||||
if (pcmd->UserCallback == ImGui_ImplDX11_DrawCallback_ResetRenderState)
|
||||
ImGui_ImplDX11_SetupRenderState(draw_data, device);
|
||||
else
|
||||
pcmd->UserCallback(draw_list, pcmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Project scissor/clipping rectangles into framebuffer space
|
||||
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
|
||||
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
|
||||
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
|
||||
continue;
|
||||
|
||||
// Apply scissor/clipping rectangle
|
||||
const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
|
||||
device->RSSetScissorRects(1, &r);
|
||||
|
||||
// Bind texture, Draw
|
||||
ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID();
|
||||
device->PSSetShaderResources(0, 1, &texture_srv);
|
||||
device->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
|
||||
}
|
||||
}
|
||||
global_idx_offset += draw_list->IdxBuffer.Size;
|
||||
global_vtx_offset += draw_list->VtxBuffer.Size;
|
||||
}
|
||||
platform_io.Renderer_RenderState = bd->RenderState = nullptr;
|
||||
|
||||
// Restore modified DX state
|
||||
device->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
|
||||
device->RSSetViewports(old.ViewportsCount, old.Viewports);
|
||||
device->RSSetState(old.RS); if (old.RS) old.RS->Release();
|
||||
device->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
|
||||
device->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
|
||||
device->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
|
||||
device->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
|
||||
device->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
|
||||
for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release();
|
||||
device->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
|
||||
device->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
|
||||
device->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
|
||||
for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release();
|
||||
device->IASetPrimitiveTopology(old.PrimitiveTopology);
|
||||
device->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
|
||||
device->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
|
||||
device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_DestroyTexture(ImTextureData* tex)
|
||||
{
|
||||
if (ImGui_ImplDX11_Texture* backend_tex = (ImGui_ImplDX11_Texture*)tex->BackendUserData)
|
||||
{
|
||||
IM_ASSERT(backend_tex->pTextureView == (ID3D11ShaderResourceView*)(intptr_t)tex->TexID);
|
||||
backend_tex->pTextureView->Release();
|
||||
backend_tex->pTexture->Release();
|
||||
IM_DELETE(backend_tex);
|
||||
|
||||
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
|
||||
tex->SetTexID(ImTextureID_Invalid);
|
||||
tex->BackendUserData = nullptr;
|
||||
}
|
||||
tex->SetStatus(ImTextureStatus_Destroyed);
|
||||
}
|
||||
|
||||
void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex)
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
if (tex->Status == ImTextureStatus_WantCreate)
|
||||
{
|
||||
// Create and upload new texture to graphics system
|
||||
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
|
||||
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
|
||||
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
|
||||
unsigned int* pixels = (unsigned int*)tex->GetPixels();
|
||||
ImGui_ImplDX11_Texture* backend_tex = IM_NEW(ImGui_ImplDX11_Texture)();
|
||||
|
||||
// Create texture
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.Width = (UINT)tex->Width;
|
||||
desc.Height = (UINT)tex->Height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
#else
|
||||
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
#endif
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
desc.CPUAccessFlags = 0;
|
||||
D3D11_SUBRESOURCE_DATA subResource;
|
||||
subResource.pSysMem = pixels;
|
||||
subResource.SysMemPitch = desc.Width * 4;
|
||||
subResource.SysMemSlicePitch = 0;
|
||||
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &backend_tex->pTexture);
|
||||
IM_ASSERT(backend_tex->pTexture != nullptr && "Backend failed to create texture!");
|
||||
|
||||
// Create texture view
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
|
||||
ZeroMemory(&srvDesc, sizeof(srvDesc));
|
||||
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
srvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
#else
|
||||
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
#endif
|
||||
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D.MipLevels = desc.MipLevels;
|
||||
srvDesc.Texture2D.MostDetailedMip = 0;
|
||||
bd->pd3dDevice->CreateShaderResourceView(backend_tex->pTexture, &srvDesc, &backend_tex->pTextureView);
|
||||
IM_ASSERT(backend_tex->pTextureView != nullptr && "Backend failed to create texture!");
|
||||
|
||||
// Store identifiers
|
||||
tex->SetTexID((ImTextureID)(intptr_t)backend_tex->pTextureView);
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
tex->BackendUserData = backend_tex;
|
||||
}
|
||||
else if (tex->Status == ImTextureStatus_WantUpdates)
|
||||
{
|
||||
// Update selected blocks. We only ever write to textures regions which have never been used before!
|
||||
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
|
||||
ImGui_ImplDX11_Texture* backend_tex = (ImGui_ImplDX11_Texture*)tex->BackendUserData;
|
||||
IM_ASSERT(backend_tex->pTextureView == (ID3D11ShaderResourceView*)(intptr_t)tex->TexID);
|
||||
for (ImTextureRect& r : tex->Updates)
|
||||
{
|
||||
D3D11_BOX box = { (UINT)r.x, (UINT)r.y, (UINT)0, (UINT)(r.x + r.w), (UINT)(r.y + r .h), (UINT)1 };
|
||||
bd->pd3dDeviceContext->UpdateSubresource(backend_tex->pTexture, 0, &box, tex->GetPixelsAt(r.x, r.y), (UINT)tex->GetPitch(), 0);
|
||||
}
|
||||
tex->SetStatus(ImTextureStatus_OK);
|
||||
}
|
||||
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
|
||||
ImGui_ImplDX11_DestroyTexture(tex);
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX11_CreateDeviceObjects()
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
if (!bd->pd3dDevice)
|
||||
return false;
|
||||
ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
|
||||
// By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
|
||||
// If you would like to use this DX11 sample code but remove this dependency you can:
|
||||
// 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution]
|
||||
// 2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL.
|
||||
// See https://github.com/ocornut/imgui/pull/638 for sources and details.
|
||||
|
||||
// spice2x: dynamically resolved; see ImGui_ImplDX11_GetD3DCompile.
|
||||
pD3DCompile D3DCompile_fn = ImGui_ImplDX11_GetD3DCompile();
|
||||
if (!D3DCompile_fn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the vertex shader
|
||||
{
|
||||
static const char* vertexShader =
|
||||
"cbuffer vertexBuffer : register(b0) \
|
||||
{\
|
||||
float4x4 ProjectionMatrix; \
|
||||
};\
|
||||
struct VS_INPUT\
|
||||
{\
|
||||
float2 pos : POSITION;\
|
||||
float4 col : COLOR0;\
|
||||
float2 uv : TEXCOORD0;\
|
||||
};\
|
||||
\
|
||||
struct PS_INPUT\
|
||||
{\
|
||||
float4 pos : SV_POSITION;\
|
||||
float4 col : COLOR0;\
|
||||
float2 uv : TEXCOORD0;\
|
||||
};\
|
||||
\
|
||||
PS_INPUT main(VS_INPUT input)\
|
||||
{\
|
||||
PS_INPUT output;\
|
||||
output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
|
||||
output.col = input.col;\
|
||||
output.uv = input.uv;\
|
||||
return output;\
|
||||
}";
|
||||
|
||||
ID3DBlob* vertexShaderBlob;
|
||||
if (FAILED(D3DCompile_fn(vertexShader, strlen(vertexShader), nullptr, nullptr, nullptr, "main", "vs_4_0", 0, 0, &vertexShaderBlob, nullptr)))
|
||||
return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
|
||||
if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), nullptr, &bd->pVertexShader) != S_OK)
|
||||
{
|
||||
vertexShaderBlob->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the input layout
|
||||
D3D11_INPUT_ELEMENT_DESC local_layout[] =
|
||||
{
|
||||
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
{ "COLOR", 0, DXGI_FORMAT_B8G8R8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
#else
|
||||
{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
#endif
|
||||
};
|
||||
if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
|
||||
{
|
||||
vertexShaderBlob->Release();
|
||||
return false;
|
||||
}
|
||||
vertexShaderBlob->Release();
|
||||
|
||||
// Create the constant buffer
|
||||
{
|
||||
D3D11_BUFFER_DESC desc = {};
|
||||
desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX11);
|
||||
desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
desc.MiscFlags = 0;
|
||||
bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVertexConstantBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the pixel shader
|
||||
{
|
||||
static const char* pixelShader =
|
||||
"struct PS_INPUT\
|
||||
{\
|
||||
float4 pos : SV_POSITION;\
|
||||
float4 col : COLOR0;\
|
||||
float2 uv : TEXCOORD0;\
|
||||
};\
|
||||
sampler sampler0;\
|
||||
Texture2D texture0;\
|
||||
\
|
||||
float4 main(PS_INPUT input) : SV_Target\
|
||||
{\
|
||||
float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \
|
||||
return out_col; \
|
||||
}";
|
||||
|
||||
ID3DBlob* pixelShaderBlob;
|
||||
if (FAILED(D3DCompile_fn(pixelShader, strlen(pixelShader), nullptr, nullptr, nullptr, "main", "ps_4_0", 0, 0, &pixelShaderBlob, nullptr)))
|
||||
return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
|
||||
if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), nullptr, &bd->pPixelShader) != S_OK)
|
||||
{
|
||||
pixelShaderBlob->Release();
|
||||
return false;
|
||||
}
|
||||
pixelShaderBlob->Release();
|
||||
}
|
||||
|
||||
// Create the blending setup
|
||||
{
|
||||
D3D11_BLEND_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.AlphaToCoverageEnable = false;
|
||||
desc.RenderTarget[0].BlendEnable = true;
|
||||
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
|
||||
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
||||
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
||||
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
||||
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
|
||||
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
||||
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
|
||||
bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState);
|
||||
}
|
||||
|
||||
// Create the rasterizer state
|
||||
{
|
||||
D3D11_RASTERIZER_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.FillMode = D3D11_FILL_SOLID;
|
||||
desc.CullMode = D3D11_CULL_NONE;
|
||||
desc.ScissorEnable = true;
|
||||
desc.DepthClipEnable = true;
|
||||
bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState);
|
||||
}
|
||||
|
||||
// Create depth-stencil State
|
||||
{
|
||||
D3D11_DEPTH_STENCIL_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.DepthEnable = false;
|
||||
desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
|
||||
desc.DepthFunc = D3D11_COMPARISON_ALWAYS;
|
||||
desc.StencilEnable = false;
|
||||
desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
|
||||
desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
|
||||
desc.BackFace = desc.FrontFace;
|
||||
bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
|
||||
}
|
||||
|
||||
// Create texture sampler
|
||||
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
|
||||
{
|
||||
D3D11_SAMPLER_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
||||
desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
desc.MipLODBias = 0.f;
|
||||
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
|
||||
desc.MinLOD = 0.f;
|
||||
desc.MaxLOD = 0.f;
|
||||
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinear);
|
||||
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
|
||||
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerNearest);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplDX11_InvalidateDeviceObjects()
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
if (!bd->pd3dDevice)
|
||||
return;
|
||||
|
||||
// Destroy all textures
|
||||
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
|
||||
if (tex->RefCount == 1)
|
||||
ImGui_ImplDX11_DestroyTexture(tex);
|
||||
|
||||
if (bd->pTexSamplerLinear) { bd->pTexSamplerLinear->Release(); bd->pTexSamplerLinear = nullptr; }
|
||||
if (bd->pTexSamplerNearest) { bd->pTexSamplerNearest->Release(); bd->pTexSamplerNearest = nullptr; }
|
||||
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||
if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
|
||||
if (bd->pDepthStencilState) { bd->pDepthStencilState->Release(); bd->pDepthStencilState = nullptr; }
|
||||
if (bd->pRasterizerState) { bd->pRasterizerState->Release(); bd->pRasterizerState = nullptr; }
|
||||
if (bd->pPixelShader) { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }
|
||||
if (bd->pVertexConstantBuffer) { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = nullptr; }
|
||||
if (bd->pInputLayout) { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }
|
||||
if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IMGUI_CHECKVERSION();
|
||||
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
||||
|
||||
// Setup backend capabilities flags
|
||||
ImGui_ImplDX11_Data* bd = IM_NEW(ImGui_ImplDX11_Data)();
|
||||
io.BackendRendererUserData = (void*)bd;
|
||||
io.BackendRendererName = "imgui_impl_dx11";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
|
||||
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||
platform_io.DrawCallback_ResetRenderState = ImGui_ImplDX11_DrawCallback_ResetRenderState;
|
||||
platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplDX11_DrawCallback_SetSamplerLinear;
|
||||
platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplDX11_DrawCallback_SetSamplerNearest;
|
||||
|
||||
// Get factory from device
|
||||
IDXGIDevice* pDXGIDevice = nullptr;
|
||||
IDXGIAdapter* pDXGIAdapter = nullptr;
|
||||
IDXGIFactory* pFactory = nullptr;
|
||||
|
||||
if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK)
|
||||
if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK)
|
||||
if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK)
|
||||
{
|
||||
bd->pd3dDevice = device;
|
||||
bd->pd3dDeviceContext = device_context;
|
||||
bd->pFactory = pFactory;
|
||||
}
|
||||
if (pDXGIDevice) pDXGIDevice->Release();
|
||||
if (pDXGIAdapter) pDXGIAdapter->Release();
|
||||
bd->pd3dDevice->AddRef();
|
||||
bd->pd3dDeviceContext->AddRef();
|
||||
|
||||
ImGui_ImplDX11_InitMultiViewportSupport();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplDX11_Shutdown()
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
|
||||
ImGui_ImplDX11_ShutdownMultiViewportSupport();
|
||||
ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
if (bd->pFactory) { bd->pFactory->Release(); }
|
||||
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
|
||||
if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); }
|
||||
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendRendererUserData = nullptr;
|
||||
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
|
||||
platform_io.ClearRendererHandlers();
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
void ImGui_ImplDX11_NewFrame()
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?");
|
||||
|
||||
if (!bd->pVertexShader)
|
||||
if (!ImGui_ImplDX11_CreateDeviceObjects())
|
||||
IM_ASSERT(0 && "ImGui_ImplDX11_CreateDeviceObjects() failed!");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
|
||||
// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
|
||||
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data.
|
||||
struct ImGui_ImplDX11_ViewportData
|
||||
{
|
||||
IDXGISwapChain* SwapChain;
|
||||
ID3D11RenderTargetView* RTView;
|
||||
|
||||
ImGui_ImplDX11_ViewportData() { SwapChain = nullptr; RTView = nullptr; }
|
||||
~ImGui_ImplDX11_ViewportData() { IM_ASSERT(SwapChain == nullptr && RTView == nullptr); }
|
||||
};
|
||||
|
||||
// Multi-Viewports: configure templates used when creating swapchains for secondary viewports. Will try them in order.
|
||||
// This is intentionally not declared in the .h file yet, so you will need to copy this declaration:
|
||||
void ImGui_ImplDX11_SetSwapChainDescs(const DXGI_SWAP_CHAIN_DESC* desc_templates, int desc_templates_count);
|
||||
void ImGui_ImplDX11_SetSwapChainDescs(const DXGI_SWAP_CHAIN_DESC* desc_templates, int desc_templates_count)
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
bd->SwapChainDescsForViewports.resize(desc_templates_count);
|
||||
memcpy(bd->SwapChainDescsForViewports.Data, desc_templates, sizeof(DXGI_SWAP_CHAIN_DESC));
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_CreateWindow(ImGuiViewport* viewport)
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
ImGui_ImplDX11_ViewportData* vd = IM_NEW(ImGui_ImplDX11_ViewportData)();
|
||||
viewport->RendererUserData = vd;
|
||||
|
||||
// PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL's WindowID).
|
||||
// Some backends will leave PlatformHandleRaw == 0, in which case we assume PlatformHandle will contain the HWND.
|
||||
HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle;
|
||||
IM_ASSERT(hwnd != 0);
|
||||
IM_ASSERT(vd->SwapChain == nullptr && vd->RTView == nullptr);
|
||||
|
||||
// Create swap chain
|
||||
HRESULT hr = DXGI_ERROR_UNSUPPORTED;
|
||||
for (const DXGI_SWAP_CHAIN_DESC& sd_template : bd->SwapChainDescsForViewports)
|
||||
{
|
||||
IM_ASSERT(sd_template.BufferDesc.Width == 0 && sd_template.BufferDesc.Height == 0 && sd_template.OutputWindow == nullptr);
|
||||
DXGI_SWAP_CHAIN_DESC sd = sd_template;
|
||||
sd.BufferDesc.Width = (UINT)viewport->Size.x;
|
||||
sd.BufferDesc.Height = (UINT)viewport->Size.y;
|
||||
sd.OutputWindow = hwnd;
|
||||
hr = bd->pFactory->CreateSwapChain(bd->pd3dDevice, &sd, &vd->SwapChain);
|
||||
if (SUCCEEDED(hr))
|
||||
break;
|
||||
}
|
||||
IM_ASSERT(SUCCEEDED(hr));
|
||||
bd->pFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES); // Disable e.g. Alt+Enter
|
||||
|
||||
// Create the render target
|
||||
if (vd->SwapChain != nullptr)
|
||||
{
|
||||
ID3D11Texture2D* pBackBuffer;
|
||||
vd->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
|
||||
bd->pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &vd->RTView);
|
||||
pBackBuffer->Release();
|
||||
}
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_DestroyWindow(ImGuiViewport* viewport)
|
||||
{
|
||||
// The main viewport (owned by the application) will always have RendererUserData == nullptr since we didn't create the data for it.
|
||||
if (ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData)
|
||||
{
|
||||
if (vd->SwapChain)
|
||||
vd->SwapChain->Release();
|
||||
vd->SwapChain = nullptr;
|
||||
if (vd->RTView)
|
||||
vd->RTView->Release();
|
||||
vd->RTView = nullptr;
|
||||
IM_DELETE(vd);
|
||||
}
|
||||
viewport->RendererUserData = nullptr;
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData;
|
||||
if (vd->RTView)
|
||||
{
|
||||
vd->RTView->Release();
|
||||
vd->RTView = nullptr;
|
||||
}
|
||||
if (vd->SwapChain)
|
||||
{
|
||||
ID3D11Texture2D* pBackBuffer = nullptr;
|
||||
vd->SwapChain->ResizeBuffers(0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0);
|
||||
vd->SwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
|
||||
if (pBackBuffer == nullptr) { fprintf(stderr, "ImGui_ImplDX11_SetWindowSize() failed creating buffers.\n"); return; }
|
||||
bd->pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &vd->RTView);
|
||||
pBackBuffer->Release();
|
||||
}
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_RenderWindow(ImGuiViewport* viewport, void*)
|
||||
{
|
||||
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
|
||||
ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData;
|
||||
ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
bd->pd3dDeviceContext->OMSetRenderTargets(1, &vd->RTView, nullptr);
|
||||
if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
|
||||
bd->pd3dDeviceContext->ClearRenderTargetView(vd->RTView, (float*)&clear_color);
|
||||
ImGui_ImplDX11_RenderDrawData(viewport->DrawData);
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_SwapBuffers(ImGuiViewport* viewport, void*)
|
||||
{
|
||||
ImGui_ImplDX11_ViewportData* vd = (ImGui_ImplDX11_ViewportData*)viewport->RendererUserData;
|
||||
if (vd->SwapChain)
|
||||
vd->SwapChain->Present(0, 0); // Present without vsync
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_InitMultiViewportSupport()
|
||||
{
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
platform_io.Renderer_CreateWindow = ImGui_ImplDX11_CreateWindow;
|
||||
platform_io.Renderer_DestroyWindow = ImGui_ImplDX11_DestroyWindow;
|
||||
platform_io.Renderer_SetWindowSize = ImGui_ImplDX11_SetWindowSize;
|
||||
platform_io.Renderer_RenderWindow = ImGui_ImplDX11_RenderWindow;
|
||||
platform_io.Renderer_SwapBuffers = ImGui_ImplDX11_SwapBuffers;
|
||||
|
||||
// Default swapchain format
|
||||
DXGI_SWAP_CHAIN_DESC sd;
|
||||
ZeroMemory(&sd, sizeof(sd));
|
||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
sd.SampleDesc.Count = 1;
|
||||
sd.SampleDesc.Quality = 0;
|
||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
sd.BufferCount = 1;
|
||||
sd.Windowed = TRUE;
|
||||
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||
sd.Flags = 0;
|
||||
ImGui_ImplDX11_SetSwapChainDescs(&sd, 1);
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX11_ShutdownMultiViewportSupport()
|
||||
{
|
||||
ImGui::DestroyPlatformWindows();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
@@ -0,0 +1,53 @@
|
||||
// dear imgui: Renderer Backend for DirectX11
|
||||
// This needs to be used along with a Platform Backend (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
|
||||
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
|
||||
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
|
||||
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
|
||||
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
|
||||
|
||||
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
|
||||
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
|
||||
// Learn about Dear ImGui:
|
||||
// - FAQ https://dearimgui.com/faq
|
||||
// - Getting Started https://dearimgui.com/getting-started
|
||||
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
|
||||
// - Introduction, links and more at the top of imgui.cpp
|
||||
|
||||
#pragma once
|
||||
#include "imgui.h" // IMGUI_IMPL_API
|
||||
#ifndef IMGUI_DISABLE
|
||||
|
||||
struct ID3D11Device;
|
||||
struct ID3D11DeviceContext;
|
||||
struct ID3D11SamplerState;
|
||||
struct ID3D11Buffer;
|
||||
|
||||
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
|
||||
|
||||
// Use if you want to reset your rendering device without losing Dear ImGui state.
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects();
|
||||
|
||||
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
|
||||
IMGUI_IMPL_API void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex);
|
||||
|
||||
// [BETA] Selected render state data shared with callbacks.
|
||||
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX11_RenderDrawData() call.
|
||||
// (Please open an issue if you feel you need access to more data)
|
||||
struct ImGui_ImplDX11_RenderState
|
||||
{
|
||||
ID3D11Device* Device;
|
||||
ID3D11DeviceContext* DeviceContext;
|
||||
ID3D11Buffer* VertexConstantBuffer;
|
||||
//ID3D11SamplerState* SamplerLinear; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerLinear)
|
||||
//ID3D11SamplerState* SamplerNearest; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerNearest)
|
||||
};
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
+156
-123
@@ -19,6 +19,7 @@
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
|
||||
// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378)
|
||||
// 2026-03-19: Fixed issue in ImGui_ImplDX9_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
|
||||
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
|
||||
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
|
||||
@@ -110,7 +111,70 @@ struct render_state_t {
|
||||
};
|
||||
|
||||
// Functions
|
||||
static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data, struct render_state_t *render_state)
|
||||
|
||||
// spice ldj
|
||||
// Save the host game's render targets / depth-stencil and redirect rendering to the
|
||||
// device back buffer. Must be called exactly once per RenderDrawData (paired with the
|
||||
// restore block at the end of RenderDrawData), NOT from SetupRenderState which can be
|
||||
// re-invoked via ImDrawCallback_ResetRenderState.
|
||||
static void ImGui_ImplDX9_RedirectToBackBuffer(LPDIRECT3DDEVICE9 device, struct render_state_t *render_state)
|
||||
{
|
||||
if (FAILED(device->GetDeviceCaps(&render_state->caps))) {
|
||||
render_state->caps.NumSimultaneousRTs = 0UL;
|
||||
}
|
||||
|
||||
// save all previous render target state
|
||||
for (size_t target = 0; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
|
||||
if (FAILED(device->GetRenderTarget(target, &render_state->render_targets[target]))) {
|
||||
render_state->render_targets[target] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// get the previous depth stencil
|
||||
if (FAILED(device->GetDepthStencilSurface(&render_state->depth_stencil))) {
|
||||
render_state->depth_stencil = nullptr;
|
||||
}
|
||||
|
||||
// set the back buffer as the current render target
|
||||
if (SUCCEEDED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &render_state->back_buffer))) {
|
||||
device->SetRenderTarget(0, render_state->back_buffer);
|
||||
device->SetDepthStencilSurface(nullptr);
|
||||
|
||||
for (size_t target = 1; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
|
||||
device->SetRenderTarget(target, nullptr);
|
||||
}
|
||||
} else {
|
||||
render_state->back_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// spice ldj
|
||||
// Restore the host game's render targets / depth-stencil saved by
|
||||
// ImGui_ImplDX9_RedirectToBackBuffer and release all references taken there.
|
||||
static void ImGui_ImplDX9_RestoreRenderTargets(LPDIRECT3DDEVICE9 device, struct render_state_t *render_state)
|
||||
{
|
||||
if (render_state->back_buffer) {
|
||||
render_state->back_buffer->Release();
|
||||
render_state->back_buffer = nullptr;
|
||||
}
|
||||
// restore previous depth stencil
|
||||
if (render_state->depth_stencil) {
|
||||
device->SetDepthStencilSurface(render_state->depth_stencil);
|
||||
render_state->depth_stencil->Release();
|
||||
render_state->depth_stencil = nullptr;
|
||||
}
|
||||
// restore all render target state
|
||||
for (size_t target = 0; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
|
||||
auto render_target = render_state->render_targets[target];
|
||||
|
||||
if (render_target) {
|
||||
device->SetRenderTarget(target, render_target);
|
||||
render_target->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
|
||||
@@ -125,37 +189,6 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data, struct render_
|
||||
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
|
||||
device->SetViewport(&vp);
|
||||
|
||||
// spice ldj
|
||||
{
|
||||
if (FAILED(device->GetDeviceCaps(&render_state->caps))) {
|
||||
render_state->caps.NumSimultaneousRTs = 0UL;
|
||||
}
|
||||
|
||||
// save all previous render target state
|
||||
for (size_t target = 0; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
|
||||
if (FAILED(device->GetRenderTarget(target, &render_state->render_targets[target]))) {
|
||||
render_state->render_targets[target] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// get the previous depth stencil
|
||||
if (FAILED(device->GetDepthStencilSurface(&render_state->depth_stencil))) {
|
||||
render_state->depth_stencil = nullptr;
|
||||
}
|
||||
|
||||
// set the back buffer as the current render target
|
||||
if (SUCCEEDED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &render_state->back_buffer))) {
|
||||
device->SetRenderTarget(0, render_state->back_buffer);
|
||||
device->SetDepthStencilSurface(nullptr);
|
||||
|
||||
for (size_t target = 1; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
|
||||
device->SetRenderTarget(target, nullptr);
|
||||
}
|
||||
} else {
|
||||
render_state->back_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
|
||||
device->SetPixelShader(nullptr);
|
||||
device->SetVertexShader(nullptr);
|
||||
@@ -214,6 +247,11 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data, struct render_
|
||||
}
|
||||
}
|
||||
|
||||
// Draw callbacks
|
||||
static void ImGui_ImplDX9_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way.
|
||||
static void ImGui_ImplDX9_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); }
|
||||
static void ImGui_ImplDX9_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT); }
|
||||
|
||||
// Render function.
|
||||
void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
{
|
||||
@@ -236,14 +274,14 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
{
|
||||
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
|
||||
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
|
||||
if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
|
||||
if (device->CreateVertexBuffer((UINT)bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
|
||||
return;
|
||||
}
|
||||
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
|
||||
{
|
||||
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
|
||||
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
|
||||
if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
|
||||
if (device->CreateIndexBuffer((UINT)bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -305,9 +343,16 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
device->SetIndices(bd->pIB);
|
||||
device->SetFVF(D3DFVF_CUSTOMVERTEX);
|
||||
|
||||
// Setup desired DX state
|
||||
|
||||
// spice ldj: redirect to back buffer once (before SetupRenderState so its
|
||||
// viewport isn't clobbered by SetRenderTarget); paired with
|
||||
// ImGui_ImplDX9_RestoreRenderTargets after the render loop.
|
||||
struct render_state_t render_state = {};
|
||||
ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
|
||||
ImGui_ImplDX9_RedirectToBackBuffer(device, &render_state);
|
||||
|
||||
|
||||
// Setup desired DX state
|
||||
ImGui_ImplDX9_SetupRenderState(draw_data);
|
||||
|
||||
// Render command lists
|
||||
// (Because we merged all buffers into a single one, we maintain our own offset into them)
|
||||
@@ -322,9 +367,8 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
if (pcmd->UserCallback != nullptr)
|
||||
{
|
||||
// User callback, registered via ImDrawList::AddCallback()
|
||||
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
|
||||
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
|
||||
ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
|
||||
if (pcmd->UserCallback == ImGui_ImplDX9_DrawCallback_ResetRenderState)
|
||||
ImGui_ImplDX9_SetupRenderState(draw_data);
|
||||
else
|
||||
pcmd->UserCallback(draw_list, pcmd);
|
||||
}
|
||||
@@ -356,27 +400,7 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
|
||||
bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 0, 0, 0);
|
||||
|
||||
// spice ldj
|
||||
{
|
||||
if (render_state.back_buffer) {
|
||||
render_state.back_buffer->Release();
|
||||
render_state.back_buffer = nullptr;
|
||||
}
|
||||
// restore previous depth stencil
|
||||
if (render_state.depth_stencil) {
|
||||
device->SetDepthStencilSurface(render_state.depth_stencil);
|
||||
render_state.depth_stencil->Release();
|
||||
render_state.depth_stencil = nullptr;
|
||||
}
|
||||
// restore all render target state
|
||||
for (size_t target = 0; target < std::min(8UL, render_state.caps.NumSimultaneousRTs); target++) {
|
||||
auto render_target = render_state.render_targets[target];
|
||||
|
||||
if (render_target) {
|
||||
device->SetRenderTarget(target, render_target);
|
||||
render_target->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui_ImplDX9_RestoreRenderTargets(device, &render_state);
|
||||
|
||||
// Restore the DX9 transform
|
||||
device->SetTransform(D3DTS_WORLD, &last_world);
|
||||
@@ -406,50 +430,6 @@ static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMA
|
||||
return support;
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IMGUI_CHECKVERSION();
|
||||
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
||||
|
||||
// Setup backend capabilities flags
|
||||
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
|
||||
io.BackendRendererUserData = (void*)bd;
|
||||
io.BackendRendererName = "imgui_impl_dx9";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
|
||||
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
|
||||
|
||||
bd->pd3dDevice = device;
|
||||
bd->pd3dDevice->AddRef();
|
||||
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
|
||||
|
||||
ImGui_ImplDX9_InitMultiViewportSupport();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_Shutdown()
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
|
||||
ImGui_ImplDX9_ShutdownMultiViewportSupport();
|
||||
ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
|
||||
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendRendererUserData = nullptr;
|
||||
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
|
||||
platform_io.ClearRendererHandlers();
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
|
||||
static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h)
|
||||
{
|
||||
@@ -557,6 +537,34 @@ void ImGui_ImplDX9_InvalidateDeviceObjects()
|
||||
ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
|
||||
}
|
||||
|
||||
// spice ldj
|
||||
// Derive io.DisplaySize from the device's swap chain back buffer dimensions, so the
|
||||
// overlay sizes itself correctly without relying on a platform backend to feed it.
|
||||
static void ImGui_ImplDX9_UpdateDisplaySize(ImGui_ImplDX9_Data* bd)
|
||||
{
|
||||
IDirect3DSwapChain9 *swap_chain = nullptr;
|
||||
if (SUCCEEDED(bd->pd3dDevice->GetSwapChain(0, &swap_chain))) {
|
||||
auto &io = ImGui::GetIO();
|
||||
|
||||
D3DPRESENT_PARAMETERS present_params {};
|
||||
|
||||
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
|
||||
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
|
||||
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
|
||||
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
|
||||
} else {
|
||||
RECT rect {};
|
||||
GetClientRect(present_params.hDeviceWindow, &rect);
|
||||
|
||||
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
|
||||
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
|
||||
}
|
||||
}
|
||||
|
||||
swap_chain->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_NewFrame()
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
@@ -564,29 +572,54 @@ void ImGui_ImplDX9_NewFrame()
|
||||
// IM_UNUSED(bd);
|
||||
|
||||
// spice ldj
|
||||
{
|
||||
IDirect3DSwapChain9 *swap_chain = nullptr;
|
||||
if (SUCCEEDED(bd->pd3dDevice->GetSwapChain(0, &swap_chain))) {
|
||||
auto &io = ImGui::GetIO();
|
||||
ImGui_ImplDX9_UpdateDisplaySize(bd);
|
||||
}
|
||||
|
||||
D3DPRESENT_PARAMETERS present_params {};
|
||||
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IMGUI_CHECKVERSION();
|
||||
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
||||
|
||||
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
|
||||
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
|
||||
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
|
||||
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
|
||||
} else {
|
||||
RECT rect {};
|
||||
GetClientRect(present_params.hDeviceWindow, &rect);
|
||||
// Setup backend capabilities flags
|
||||
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
|
||||
io.BackendRendererUserData = (void*)bd;
|
||||
io.BackendRendererName = "imgui_impl_dx9";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
|
||||
|
||||
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
|
||||
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
|
||||
}
|
||||
}
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
|
||||
platform_io.DrawCallback_ResetRenderState = ImGui_ImplDX9_DrawCallback_ResetRenderState;
|
||||
platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplDX9_DrawCallback_SetSamplerLinear;
|
||||
platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplDX9_DrawCallback_SetSamplerNearest;
|
||||
|
||||
swap_chain->Release();
|
||||
}
|
||||
}
|
||||
bd->pd3dDevice = device;
|
||||
bd->pd3dDevice->AddRef();
|
||||
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
|
||||
|
||||
ImGui_ImplDX9_InitMultiViewportSupport();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_Shutdown()
|
||||
{
|
||||
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
|
||||
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
|
||||
ImGui_ImplDX9_ShutdownMultiViewportSupport();
|
||||
ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
|
||||
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendRendererUserData = nullptr;
|
||||
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
|
||||
platform_io.ClearRendererHandlers();
|
||||
IM_DELETE(bd);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------
|
||||
|
||||
Vendored
+199
-105
@@ -1,4 +1,4 @@
|
||||
// dear imgui, v1.92.7
|
||||
// dear imgui, v1.92.9 WIP
|
||||
// (main code and documentation)
|
||||
|
||||
// Help:
|
||||
@@ -402,7 +402,34 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures:
|
||||
you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos)
|
||||
- likewise io.MousePos and GetMousePos() will use OS coordinates.
|
||||
If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos.
|
||||
|
||||
- 2026/06/02 (1.92.9) - TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead.
|
||||
- 2026/05/07 (1.92.8) - DrawList: swapped the last two arguments of AddRect(), AddPolyline(), PathStroke().
|
||||
- Before: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f);
|
||||
- After: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0);
|
||||
- Before: void ImDrawList::AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness);
|
||||
- After: void ImDrawList::AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0);
|
||||
- Before: void ImDrawList::PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f);
|
||||
- After: void ImDrawList::PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0);
|
||||
Added inline redirection functions when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is off.
|
||||
Marked the old functions are =delete when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is on, to allow for better type-checking.
|
||||
Effectively the typical call site is changing from:
|
||||
- Before: window->DrawList->AddRect(p_min, p_max, color, rounding, ImDrawFlags_None, border_size);
|
||||
- After: window->DrawList->AddRect(p_min, p_max, color, rounding, border_size);
|
||||
Notes:
|
||||
- Users of C++ and other languages with type-checking will be notified at compile-time of any mistakes.
|
||||
- Users of high-level bindings or languages with no type-checking will be notified at runtime via an assert for invalid flags value.
|
||||
If you are a binding maintainer consider doing something to facilitate transition or error detection.
|
||||
- This is perhaps the worst breaking change in our history :( but it makes ImDrawList function signatures consistent.
|
||||
As we are aiming to add flags and features to variety of ImDrawList functions, that consistency becomes more important.
|
||||
The new order is also more convenient as `flags` are less frequently used than `thickness` in real code.
|
||||
- As a general policy in Dear ImGui, all our flags default to 0 so ImDrawFlags_None was likely written 0 in some call sites.
|
||||
- Consider adding `#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS` in your imconfig.h, even temporarily, to clean up legacy code.
|
||||
- 2026/05/07 (1.92.8) - DrawList: changed value of `ImDrawFlags_Closed`. It was previously advertised as "always == 1" when introduced in 1.82 (2021/02), in order to facilitate backward compatibility with the legacy `bool closed` flag.
|
||||
This guarantee has been removed. The bit is reserved and `AddPolyline()`, `PathStroke()` will assert when it is used.
|
||||
- 2026/04/23 (1.92.8) - DrawList: obsoleted `ImDrawCallback_ResetRenderState` in favor of using `ImGui::GetPlatformIO().DrawCallback_ResetRenderState`, which is part of our new standard draw callbacks. (#9378)
|
||||
- 2026/04/22 (1.92.8) - Backends: Vulkan: redesigned to use separate ImageView + Sampler instead of Combined Image Sampler.
|
||||
- When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler.
|
||||
- When creating your own descriptor pool (instead of letting backend creates its own): need at least IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE + IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE descriptors of type VK_DESCRIPTOR_TYPE_SAMPLER.
|
||||
- 2026/03/19 (1.92.7) - MultiSelect: renamed ImGuiMultiSelectFlags_SelectOnClick to ImGuiMultiSelectFlags_SelectOnAuto.
|
||||
- 2026/02/26 (1.92.7) - Separator: fixed a legacy quirk where Separator() was submitting a zero-height item for layout purpose, even though it draws a 1-pixel separator.
|
||||
The fix could affect code e.g. computing height from multiple widgets in order to allocate vertical space for a footer or multi-line status bar. (#2657, #9263)
|
||||
@@ -1524,7 +1551,7 @@ ImGuiStyle::ImGuiStyle()
|
||||
TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
|
||||
TabBorderSize = 0.0f; // Thickness of border around tabs.
|
||||
TabMinWidthBase = 1.0f; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected.
|
||||
TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy.
|
||||
TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. FLT_MAX: never shrink, will behave like ImGuiTabBarFlags_FittingPolicyScroll.
|
||||
TabCloseButtonMinWidthSelected = -1.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.
|
||||
TabCloseButtonMinWidthUnselected = 0.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected.
|
||||
TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus.
|
||||
@@ -1541,6 +1568,7 @@ ImGuiStyle::ImGuiStyle()
|
||||
ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
|
||||
ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
|
||||
SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.
|
||||
InputTextCursorSize = 1.0f; // Thickness of cursor/caret in InputText().
|
||||
SeparatorSize = 1.0f; // Thickness of border in Separator().
|
||||
SeparatorTextBorderSize = 3.0f; // Thickness of border in SeparatorText().
|
||||
SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).
|
||||
@@ -1573,6 +1601,7 @@ ImGuiStyle::ImGuiStyle()
|
||||
|
||||
|
||||
// Scale all spacing/padding/thickness values. Do not scale fonts.
|
||||
// Consider not calling this if your initial scale factor if <1.0.
|
||||
// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times.
|
||||
void ImGuiStyle::ScaleAllSizes(float scale_factor)
|
||||
{
|
||||
@@ -1617,6 +1646,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor)
|
||||
DragDropTargetBorderSize = ImTrunc(DragDropTargetBorderSize * scale_factor);
|
||||
DragDropTargetPadding = ImTrunc(DragDropTargetPadding * scale_factor);
|
||||
ColorMarkerSize = ImTrunc(ColorMarkerSize * scale_factor);
|
||||
InputTextCursorSize = ImTrunc(InputTextCursorSize * scale_factor);
|
||||
SeparatorSize = ImTrunc(SeparatorSize * scale_factor);
|
||||
SeparatorTextBorderSize = ImTrunc(SeparatorTextBorderSize * scale_factor);
|
||||
SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor);
|
||||
@@ -3454,9 +3484,6 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper)
|
||||
if (clipper->ItemsHeight <= 0.0f)
|
||||
{
|
||||
IM_ASSERT(data->StepNo == 1);
|
||||
if (table)
|
||||
IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y);
|
||||
|
||||
bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision((float)clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y);
|
||||
if (affected_by_floating_point_precision)
|
||||
{
|
||||
@@ -3470,7 +3497,14 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper)
|
||||
}
|
||||
if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode.
|
||||
return false;
|
||||
IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!");
|
||||
if (clipper->ItemsHeight <= 0.0f)
|
||||
{
|
||||
IM_ASSERT_USER_ERROR(clipper->ItemsHeight > 0.0f, "ImGuiListClipper: Failed to calculate item height! First item hasn't been submitted by user code, or has not moved the cursor vertically!");
|
||||
return false;
|
||||
}
|
||||
if (table)
|
||||
IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y);
|
||||
|
||||
calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards.
|
||||
}
|
||||
|
||||
@@ -3516,12 +3550,14 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper)
|
||||
// FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos.
|
||||
// RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that.
|
||||
// As a workaround we currently half ItemSpacing worth on each side.
|
||||
min_y -= g.Style.ItemSpacing.y;
|
||||
max_y += g.Style.ItemSpacing.y;
|
||||
float pad_y = g.Style.ItemSpacing.y;
|
||||
min_y -= pad_y;
|
||||
max_y += pad_y;
|
||||
|
||||
// Box-select on 2D area requires different clipping.
|
||||
// (best adding pad_y here than in BeginBoxSelect() as we are closer to current state)
|
||||
if (bs->UnclipMode)
|
||||
data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0));
|
||||
data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y - pad_y, bs->UnclipRect.Max.y + pad_y, 0, 0));
|
||||
}
|
||||
|
||||
// Add main visible range
|
||||
@@ -3720,6 +3756,7 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] =
|
||||
{ 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign
|
||||
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesSize)}, // ImGuiStyleVar_TreeLinesSize
|
||||
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesRounding)}, // ImGuiStyleVar_TreeLinesRounding
|
||||
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DragDropTargetRounding)}, // ImGuiStyleVar_DragDropTargetRounding
|
||||
{ 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
|
||||
{ 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign
|
||||
{ 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorSize)}, // ImGuiStyleVar_SeparatorSize
|
||||
@@ -3821,6 +3858,7 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx)
|
||||
case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered";
|
||||
case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive";
|
||||
case ImGuiCol_CheckMark: return "CheckMark";
|
||||
case ImGuiCol_CheckboxSelectedBg: return "CheckboxSelectedBg";
|
||||
case ImGuiCol_SliderGrab: return "SliderGrab";
|
||||
case ImGuiCol_SliderGrabActive: return "SliderGrabActive";
|
||||
case ImGuiCol_Button: return "Button";
|
||||
@@ -3903,7 +3941,7 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool
|
||||
else
|
||||
{
|
||||
if (!text_end)
|
||||
text_end = text + ImStrlen(text); // FIXME-OPT
|
||||
text_end = text + ImStrlen(text); // FIXME-OPT (not reached by our internal calls)
|
||||
text_display_end = text_end;
|
||||
}
|
||||
|
||||
@@ -3921,7 +3959,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end
|
||||
ImGuiWindow* window = g.CurrentWindow;
|
||||
|
||||
if (!text_end)
|
||||
text_end = text + ImStrlen(text); // FIXME-OPT
|
||||
text_end = text + ImStrlen(text); // FIXME-OPT (not reached by our internal calls)
|
||||
|
||||
if (text != text_end)
|
||||
{
|
||||
@@ -3990,8 +4028,8 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con
|
||||
text_end_full = FindRenderedTextEnd(text);
|
||||
const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f);
|
||||
|
||||
//draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 6), IM_COL32(0, 0, 255, 255));
|
||||
//draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y - 2), ImVec2(ellipsis_max_x, pos_max.y + 3), IM_COL32(0, 255, 0, 255));
|
||||
//draw_list->AddLineV(pos_max.x, pos_min.y - 4, pos_max.y + 6, IM_COL32(0, 0, 255, 255));
|
||||
//draw_list->AddLineV(ellipsis_max_x, pos_min.y - 2, pos_max.y + 3, IM_COL32(0, 255, 0, 255));
|
||||
|
||||
// FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels.
|
||||
if (text_size.x > pos_max.x - pos_min.x)
|
||||
@@ -4036,8 +4074,8 @@ void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders
|
||||
const float border_size = g.Style.FrameBorderSize;
|
||||
if (borders && border_size > 0.0f)
|
||||
{
|
||||
window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size);
|
||||
window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size);
|
||||
window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, border_size);
|
||||
window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, border_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4048,8 +4086,8 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
|
||||
const float border_size = g.Style.FrameBorderSize;
|
||||
if (border_size > 0.0f)
|
||||
{
|
||||
window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size);
|
||||
window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size);
|
||||
window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, border_size);
|
||||
window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, border_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4084,7 +4122,7 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl
|
||||
const float thickness = 2.0f;
|
||||
if (flags & ImGuiNavRenderCursorFlags_Compact)
|
||||
{
|
||||
window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness);
|
||||
window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, thickness);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -4093,7 +4131,7 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl
|
||||
bool fully_visible = window->ClipRect.Contains(display_rect);
|
||||
if (!fully_visible)
|
||||
window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
|
||||
window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness);
|
||||
window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, thickness);
|
||||
if (!fully_visible)
|
||||
window->DrawList->PopClipRect();
|
||||
}
|
||||
@@ -4127,7 +4165,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso
|
||||
float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI);
|
||||
float a_max = a_min + IM_PI * 1.65f;
|
||||
draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max);
|
||||
draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale);
|
||||
draw_list->PathStroke(col_fill, 3.0f * scale);
|
||||
}
|
||||
draw_list->PopTexture();
|
||||
}
|
||||
@@ -4229,7 +4267,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
|
||||
IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)();
|
||||
if (shared_font_atlas == NULL)
|
||||
IO.Fonts->OwnerContext = this;
|
||||
WithinEndChildID = 0;
|
||||
WithinEndChildID = WithinEndPopupID = 0;
|
||||
TestEngine = NULL;
|
||||
|
||||
InputEventsNextMouseSource = ImGuiMouseSource_Mouse;
|
||||
@@ -4537,7 +4575,7 @@ void ImGui::Shutdown()
|
||||
for (ImFontAtlas* atlas : g.FontAtlases)
|
||||
{
|
||||
UnregisterFontAtlas(atlas);
|
||||
if (atlas->RefCount == 0)
|
||||
if (atlas->RefCount == 0 && atlas->OwnerContext == &g)
|
||||
{
|
||||
atlas->Locked = false;
|
||||
IM_DELETE(atlas);
|
||||
@@ -4795,7 +4833,8 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
|
||||
g.ActiveIdIsJustActivated = (g.ActiveId != id);
|
||||
if (g.ActiveIdIsJustActivated)
|
||||
{
|
||||
IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() old:0x%08X (window \"%s\") -> new:0x%08X (window \"%s\")\n", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : "", id, window ? window->Name : "");
|
||||
IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() 0x%08X in \"%s\"%*s(previously 0x%08X in \"%s\")\n", id, window ? window->Name : "",
|
||||
ImMax(0, 20 - (int)(window ? strlen(window->Name) : 0)), "", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : "");
|
||||
g.ActiveIdTimer = 0.0f;
|
||||
g.ActiveIdHasBeenPressedBefore = false;
|
||||
g.ActiveIdHasBeenEditedBefore = false;
|
||||
@@ -4851,8 +4890,12 @@ void ImGui::MarkItemEdited(ImGuiID id)
|
||||
// This marking is to be able to provide info for IsItemDeactivatedAfterEdit().
|
||||
// ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data.
|
||||
ImGuiContext& g = *GImGui;
|
||||
|
||||
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_EditedInternal;
|
||||
if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited)
|
||||
return;
|
||||
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
|
||||
|
||||
if (g.ActiveId == id || g.ActiveId == 0)
|
||||
{
|
||||
// FIXME: Can't we fully rely on LastItemData yet?
|
||||
@@ -4866,9 +4909,6 @@ void ImGui::MarkItemEdited(ImGuiID id)
|
||||
// We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714)
|
||||
// FIXME: This assert is getting a bit meaningless over time. It helped detect some unusual use cases but eventually it is becoming an unnecessary restriction.
|
||||
IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive));
|
||||
|
||||
//IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
|
||||
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
|
||||
}
|
||||
|
||||
bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)
|
||||
@@ -5042,7 +5082,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag
|
||||
{
|
||||
g.HoveredIdPreviousFrameItemCount++;
|
||||
if (g.DebugDrawIdConflictsId == id)
|
||||
window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f);
|
||||
window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, 2.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5487,14 +5527,15 @@ void ImGui::UpdateMouseMovingWindowEndFrame()
|
||||
|
||||
// Click on empty space to focus window and start moving
|
||||
// (after we're done with all our widgets, so e.g. clicking on docking tab-bar which have set HoveredId already and not get us here!)
|
||||
if (g.IO.MouseClicked[0])
|
||||
if (IsMouseClicked(0, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner))
|
||||
{
|
||||
// Handle the edge case of a popup being closed while clicking in its empty space.
|
||||
// If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more.
|
||||
ImGuiWindow* hovered_root = hovered_window ? hovered_window->RootWindow : NULL;
|
||||
const bool is_closed_popup = hovered_root && (hovered_root->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(hovered_root->PopupId, ImGuiPopupFlags_AnyPopupLevel);
|
||||
const bool is_queued_focus_request = g.NavMoveSubmitted && (g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi);
|
||||
|
||||
if (hovered_window != NULL && !is_closed_popup)
|
||||
if (hovered_window != NULL && !is_closed_popup && !is_queued_focus_request)
|
||||
{
|
||||
StartMouseMovingWindow(hovered_window); //-V595
|
||||
|
||||
@@ -5525,7 +5566,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame()
|
||||
// With right mouse button we close popups without changing focus based on where the mouse is aimed
|
||||
// Instead, focus will be restored to the window under the bottom-most closed popup.
|
||||
// (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger)
|
||||
if (g.IO.MouseClicked[1] && g.HoveredId == 0)
|
||||
if (g.HoveredId == 0 && IsMouseClicked(1, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner))
|
||||
{
|
||||
// Find the top-most window between HoveredWindow and the top-most Modal Window.
|
||||
// This is where we can trim the popup stack.
|
||||
@@ -5920,7 +5961,7 @@ void ImGui::NewFrame()
|
||||
g.CurrentWindowStack.resize(0);
|
||||
g.BeginPopupStack.resize(0);
|
||||
g.ItemFlagsStack.resize(0);
|
||||
g.ItemFlagsStack.push_back(ImGuiItemFlags_AutoClosePopups); // Default flags
|
||||
g.ItemFlagsStack.push_back(ImGuiItemFlags_Default_); // Default flags
|
||||
g.CurrentItemFlags = g.ItemFlagsStack.back();
|
||||
g.GroupStack.resize(0);
|
||||
|
||||
@@ -6090,14 +6131,6 @@ void ImGui::PopClipRect()
|
||||
window->ClipRect = window->DrawList->_ClipRectStack.back();
|
||||
}
|
||||
|
||||
static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window)
|
||||
{
|
||||
for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--)
|
||||
if (IsWindowActiveAndVisible(window->DC.ChildWindows[n]))
|
||||
return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]);
|
||||
return window;
|
||||
}
|
||||
|
||||
static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col)
|
||||
{
|
||||
if ((col & IM_COL32_A_MASK) == 0)
|
||||
@@ -6199,7 +6232,7 @@ static void ImGui::RenderDimmedBackgrounds()
|
||||
if (window->DrawList->CmdBuffer.Size == 0)
|
||||
window->DrawList->AddDrawCmd();
|
||||
window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size);
|
||||
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f); // FIXME-DPI
|
||||
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 3.0f); // FIXME-DPI
|
||||
window->DrawList->PopClipRect();
|
||||
}
|
||||
|
||||
@@ -6422,11 +6455,9 @@ ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_tex
|
||||
ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
|
||||
|
||||
// Round
|
||||
// FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out.
|
||||
// FIXME: Investigate using ceilf or e.g.
|
||||
// - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c
|
||||
// - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html
|
||||
text_size.x = IM_TRUNC(text_size.x + 0.99999f);
|
||||
// (see 7b0bf230, 4622fa4b6, #791 for details about this.)
|
||||
// FIXME: Add a way to disable this.
|
||||
text_size.x = ImCeilFast(text_size.x);
|
||||
|
||||
return text_size;
|
||||
}
|
||||
@@ -6566,11 +6597,12 @@ bool ImGui::IsItemToggledOpen()
|
||||
return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false;
|
||||
}
|
||||
|
||||
// Call after a Selectable() or TreeNode() involved in multi-selection.
|
||||
// Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose.
|
||||
// This is only meant to be called inside a BeginMultiSelect()/EndMultiSelect() block.
|
||||
// (Outside of multi-select, it would be misleading/ambiguous to report this signal, as widgets
|
||||
// return e.g. a pressed event and user code is in charge of altering selection in ways we cannot predict.)
|
||||
// Call after a Selectable() or TreeNode() items inside a BeginMultiSelect()/EndMultiSelect() scope.
|
||||
// - Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose.
|
||||
// Outside of a multi-select block:
|
||||
// - It would be misleading/ambiguous to report this signal, as widgets return e.g. a pressed event,
|
||||
// and user code is in charge of altering selection in ways we cannot predict.
|
||||
// Prefer using 'if (IsItemClicked() && !IsItemToggledOpen())' for a manual reimplementation of selection.
|
||||
bool ImGui::IsItemToggledSelection()
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
@@ -6717,7 +6749,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I
|
||||
window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking;
|
||||
window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
|
||||
if (child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize))
|
||||
window_flags |= ImGuiWindowFlags_AlwaysAutoResize;
|
||||
window_flags |= ImGuiWindowFlags_AlwaysAutoResize; // FIXME: Would be sane to not make single-axis flag set this. (#9355)
|
||||
if ((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0)
|
||||
window_flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
|
||||
|
||||
@@ -6866,6 +6898,14 @@ void ImGui::EndChild()
|
||||
g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
|
||||
}
|
||||
|
||||
ImGuiWindow* ImGui::FindFrontMostVisibleChildWindow(ImGuiWindow* window)
|
||||
{
|
||||
for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--)
|
||||
if (IsWindowActiveAndVisible(window->DC.ChildWindows[n]))
|
||||
return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]);
|
||||
return window;
|
||||
}
|
||||
|
||||
static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
|
||||
{
|
||||
window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
|
||||
@@ -7050,8 +7090,8 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont
|
||||
const float decoration_h_without_scrollbars = window->DecoOuterSizeY1 + window->DecoOuterSizeY2 - window->ScrollbarSizes.y;
|
||||
ImVec2 size_pad = window->WindowPadding * 2.0f;
|
||||
ImVec2 size_desired;
|
||||
size_desired[ImGuiAxis_X] = (axis_mask & 1) ? size_contents.x + size_pad.x + decoration_w_without_scrollbars : window->Size.x;
|
||||
size_desired[ImGuiAxis_Y] = (axis_mask & 2) ? size_contents.y + size_pad.y + decoration_h_without_scrollbars : window->Size.y;
|
||||
size_desired.x = (axis_mask & 1) ? size_contents.x + size_pad.x + decoration_w_without_scrollbars : window->Size.x;
|
||||
size_desired.y = (axis_mask & 2) ? size_contents.y + size_pad.y + decoration_h_without_scrollbars : window->Size.y;
|
||||
|
||||
// Determine maximum window size
|
||||
// Child windows are laid within their parent (unless they are also popups/menus) and thus have no restriction
|
||||
@@ -7078,8 +7118,10 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont
|
||||
// When the window cannot fit all contents (either because of constraints, either because screen is too small),
|
||||
// we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding.
|
||||
ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit);
|
||||
bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
||||
bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_h_without_scrollbars < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||
float size_contents_for_scrollbar_x = (axis_mask & 1) ? size_contents.x : window->ContentSize.x; // See #9352. In theory this should use same logic as `window->ScrollbarY = ...` codepath in Begin(). Needs some plumbling.
|
||||
float size_contents_for_scrollbar_y = (axis_mask & 2) ? size_contents.y : window->ContentSize.y;
|
||||
bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x < size_contents_for_scrollbar_x + size_pad.x + decoration_w_without_scrollbars && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
||||
bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y < size_contents_for_scrollbar_y + size_pad.y + decoration_h_without_scrollbars && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||
if (will_have_scrollbar_x)
|
||||
size_auto_fit.y += style.ScrollbarSize;
|
||||
if (will_have_scrollbar_y)
|
||||
@@ -7433,7 +7475,7 @@ static void RenderWindowOuterSingleBorder(ImGuiWindow* window, int border_n, ImU
|
||||
const ImRect border_r = GetResizeBorderRect(window, border_n, rounding, 0.0f);
|
||||
window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle);
|
||||
window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f);
|
||||
window->DrawList->PathStroke(border_col, ImDrawFlags_None, border_size);
|
||||
window->DrawList->PathStroke(border_col, border_size);
|
||||
}
|
||||
|
||||
static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window)
|
||||
@@ -7442,7 +7484,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window)
|
||||
const float border_size = window->WindowBorderSize;
|
||||
const ImU32 border_col = GetColorU32(ImGuiCol_Border);
|
||||
if (border_size > 0.0f && (window->Flags & ImGuiWindowFlags_NoBackground) == 0)
|
||||
window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, 0, window->WindowBorderSize);
|
||||
window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, window->WindowBorderSize);
|
||||
else if (border_size > 0.0f)
|
||||
{
|
||||
if (window->ChildFlags & ImGuiChildFlags_ResizeX) // Similar code as 'resize_border_mask' computation in UpdateWindowManualResize() but we specifically only always draw explicit child resize border.
|
||||
@@ -7459,7 +7501,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window)
|
||||
if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
|
||||
{
|
||||
float y = window->Pos.y + window->TitleBarHeight - 1;
|
||||
window->DrawList->AddLine(ImVec2(window->Pos.x + border_size * 0.5f, y), ImVec2(window->Pos.x + window->Size.x - border_size * 0.5f, y), border_col, g.Style.FrameBorderSize);
|
||||
window->DrawList->AddLineH(window->Pos.x + border_size * 0.5f, window->Pos.x + window->Size.x - border_size * 0.5f, y, border_col, g.Style.FrameBorderSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7569,7 +7611,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
|
||||
menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them.
|
||||
window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop);
|
||||
if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)
|
||||
window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
|
||||
window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5f, menu_bar_rect.Max.x - window_border_size * 0.5f, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
|
||||
}
|
||||
|
||||
// Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock
|
||||
@@ -8357,12 +8399,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
|
||||
ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2));
|
||||
ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + scrollbar_sizes_from_last_frame;
|
||||
ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f;
|
||||
float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x;
|
||||
float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y;
|
||||
float size_for_scrollbars_x = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x;
|
||||
float size_for_scrollbars_y = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y;
|
||||
bool scrollbar_x_prev = window->ScrollbarX;
|
||||
//bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons?
|
||||
window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
|
||||
window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
|
||||
window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_for_scrollbars_y) && !(flags & ImGuiWindowFlags_NoScrollbar));
|
||||
window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_for_scrollbars_x - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
|
||||
|
||||
// Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488)
|
||||
// (Feedback loops of this sort can manifest in various situations, but combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent cause.
|
||||
@@ -8377,7 +8419,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
|
||||
window->ScrollbarXStabilizeEnabled = scrollbar_x_stabilize;
|
||||
|
||||
if (window->ScrollbarX && !window->ScrollbarY)
|
||||
window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);
|
||||
window->ScrollbarY = (needed_size_from_last_frame.y > size_for_scrollbars_y - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);
|
||||
window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
|
||||
|
||||
// Amend the partially filled window->DecorationXXX values.
|
||||
@@ -8546,9 +8588,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
|
||||
window->DC.LayoutType = ImGuiLayoutType_Vertical;
|
||||
window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
|
||||
|
||||
// Default item width. Make it proportional to window size if window manually resizes
|
||||
const bool is_resizable_window = (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize));
|
||||
if (is_resizable_window)
|
||||
// Default item width. Make it proportional to window size if window can be manually resized.
|
||||
// (we cannot use AutoFitFramesX/AutoFitFramesY which is a temporary state)
|
||||
bool is_resizable_width;
|
||||
if (flags & ImGuiWindowFlags_ChildWindow)
|
||||
is_resizable_width = (window->Size.x > 0.0f) && !(window->ChildFlags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AlwaysAutoResize));
|
||||
else
|
||||
is_resizable_width = (window->Size.x > 0.0f) && !(flags & ImGuiWindowFlags_AlwaysAutoResize);
|
||||
if (is_resizable_width)
|
||||
window->DC.ItemWidthDefault = ImTrunc(window->Size.x * 0.65f);
|
||||
else
|
||||
window->DC.ItemWidthDefault = ImTrunc(g.FontSize * 16.0f);
|
||||
@@ -8770,6 +8817,8 @@ void ImGui::End()
|
||||
ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back();
|
||||
|
||||
// Error checking: verify that user doesn't directly call End() on a child window.
|
||||
if (window->Flags & ImGuiWindowFlags_Popup)
|
||||
IM_ASSERT_USER_ERROR(g.WithinEndPopupID == window->ID, "Must call EndPopup() and not End()!");
|
||||
if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->DockIsActive)
|
||||
IM_ASSERT_USER_ERROR(g.WithinEndChildID == window->ID, "Must call EndChild() and not End()!");
|
||||
|
||||
@@ -9367,6 +9416,17 @@ void ImGui::PopFocusScope()
|
||||
g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back().ID : 0;
|
||||
}
|
||||
|
||||
bool ImGui::IsInNavFocusRoute(ImGuiID focus_scope_id)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
if (g.NavFocusScopeId == focus_scope_id)
|
||||
return true;
|
||||
for (const ImGuiFocusScopeData& focus_scope : g.NavFocusRoute)
|
||||
if (focus_scope.ID == focus_scope_id)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImGui::SetNavFocusScope(ImGuiID focus_scope_id)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
@@ -9540,6 +9600,8 @@ static void ImGui::UpdateTexturesNewFrame()
|
||||
IM_ASSERT(atlas->RendererHasTextures == has_textures);
|
||||
}
|
||||
}
|
||||
for (ImTextureData* tex : g.UserTextures)
|
||||
ImTextureDataUpdateNewFrame(tex);
|
||||
}
|
||||
|
||||
// Build a single texture list
|
||||
@@ -9601,7 +9663,7 @@ ImFont* ImGui::GetDefaultFont()
|
||||
return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0];
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: DO NOT USE YET.
|
||||
// EXPERIMENTAL. Use ImTextureDataQueueUpload() to queue updates. Textures logic will be automatically be updated in NewFrame().
|
||||
void ImGui::RegisterUserTexture(ImTextureData* tex)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
@@ -9723,7 +9785,7 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling)
|
||||
}
|
||||
|
||||
g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL;
|
||||
g.FontBakedScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f;
|
||||
g.FontBakedScale = (g.FontBaked != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f;
|
||||
g.DrawListSharedData.FontScale = g.FontBakedScale;
|
||||
}
|
||||
|
||||
@@ -10936,15 +10998,21 @@ void ImGui::UpdateMouseWheel()
|
||||
LockWheelingWindow(NULL, 0.0f);
|
||||
}
|
||||
|
||||
ImVec2 wheel;
|
||||
wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheelH : 0.0f;
|
||||
wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheel : 0.0f;
|
||||
|
||||
//IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y);
|
||||
ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
|
||||
if (!mouse_window || mouse_window->Collapsed)
|
||||
return;
|
||||
|
||||
ImGuiID owner_id = mouse_window->ID;
|
||||
ImVec2 wheel;
|
||||
wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, owner_id) ? g.IO.MouseWheelH : 0.0f;
|
||||
wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, owner_id) ? g.IO.MouseWheel : 0.0f;
|
||||
//IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y);
|
||||
if (g.WheelingWindow != NULL)
|
||||
{
|
||||
SetKeyOwner(ImGuiKey_MouseWheelX, owner_id);
|
||||
SetKeyOwner(ImGuiKey_MouseWheelY, owner_id);
|
||||
}
|
||||
|
||||
// Zoom / Scale window
|
||||
// FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned.
|
||||
if (wheel.y != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
|
||||
@@ -11253,6 +11321,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id)
|
||||
// - SetKeyOwner(..., None) : clears owner
|
||||
// - SetKeyOwner(..., Any, !Lock) : illegal (assert)
|
||||
// - SetKeyOwner(..., Any or None, Lock) : set lock
|
||||
// Ownership is automatically released on the frame after a release, see code in UpdateKeyboardInputs().
|
||||
void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
@@ -11279,30 +11348,34 @@ void ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, I
|
||||
if (key_chord & ~ImGuiMod_Mask_) { SetKeyOwner((ImGuiKey)(key_chord & ~ImGuiMod_Mask_), owner_id, flags); }
|
||||
}
|
||||
|
||||
// This is more or less equivalent to:
|
||||
// This is more or less equivalent to a fancier version of:
|
||||
// if (IsItemHovered() || IsItemActive())
|
||||
// SetKeyOwner(key, GetItemID());
|
||||
// Extensive uses of that (e.g. many calls for a single item) may want to manually perform the tests once and then call SetKeyOwner() multiple times.
|
||||
// More advanced usage scenarios may want to call SetKeyOwner() manually based on different condition.
|
||||
// Worth noting is that only one item can be hovered and only one item can be active, therefore this usage pattern doesn't need to bother with routing and priority.
|
||||
void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags)
|
||||
bool ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiID id = g.LastItemData.ID;
|
||||
if (id == 0 || (g.HoveredId != id && g.ActiveId != id))
|
||||
return;
|
||||
return false;
|
||||
if ((flags & ImGuiInputFlags_CondMask_) == 0)
|
||||
flags |= ImGuiInputFlags_CondDefault_;
|
||||
if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) || (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive)))
|
||||
{
|
||||
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) == 0); // Passing flags not supported by this function!
|
||||
if (!TestKeyOwner(key, id))
|
||||
return false;
|
||||
SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImGui::SetItemKeyOwner(ImGuiKey key)
|
||||
bool ImGui::SetItemKeyOwner(ImGuiKey key)
|
||||
{
|
||||
SetItemKeyOwner(key, ImGuiInputFlags_None);
|
||||
return SetItemKeyOwner(key, ImGuiInputFlags_None);
|
||||
}
|
||||
|
||||
// This is the only public API until we expose owner_id versions of the API as replacements.
|
||||
@@ -11443,7 +11516,8 @@ bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, si
|
||||
// to extend contents size of our parent container (e.g. window contents size, which is used for auto-resizing
|
||||
// windows, table column contents size used for auto-resizing columns, group size).
|
||||
// This was causing issues and ambiguities and we needed to retire that.
|
||||
// From 1.89, extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED.
|
||||
// 2022/08/05 (1.89): extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED. However we gated the new logic behind a '#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS' block.
|
||||
// 2025/06/25 (1.92): removed the legacy path and turned into an assert. It was a mistake that there was a #ifndef before: our obsolescence schedule gets pushed back a bit more :(
|
||||
//
|
||||
// Previously this would make the window content size ~200x200:
|
||||
// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK ANYMORE
|
||||
@@ -13102,6 +13176,17 @@ bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags ext
|
||||
return false;
|
||||
}
|
||||
|
||||
// As we bypass BeginChild(), set ImGuiChildFlags_AlwaysAutoResize as it is checked independently from ImGuiWindowFlags_AlwaysAutoResize for now (see #9355)
|
||||
// Ideally we should remove setting ImGuiWindowFlags_AlwaysAutoResize in BeginChild().
|
||||
if ((extra_window_flags & ImGuiWindowFlags_ChildWindow) && (extra_window_flags & ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags)
|
||||
g.NextWindowData.ChildFlags |= ImGuiChildFlags_AlwaysAutoResize;
|
||||
else
|
||||
g.NextWindowData.ChildFlags = ImGuiChildFlags_AlwaysAutoResize;
|
||||
g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags;
|
||||
}
|
||||
|
||||
char name[128];
|
||||
IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu);
|
||||
ImFormatString(name, IM_COUNTOF(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth
|
||||
@@ -13174,10 +13259,13 @@ void ImGui::EndPopup()
|
||||
NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY);
|
||||
|
||||
// Child-popups don't need to be laid out
|
||||
const ImGuiID backup_within_end_popup_id = g.WithinEndPopupID;
|
||||
const ImGuiID backup_within_end_child_id = g.WithinEndChildID;
|
||||
g.WithinEndPopupID = window->ID;
|
||||
if (window->Flags & ImGuiWindowFlags_ChildWindow)
|
||||
g.WithinEndChildID = window->ID;
|
||||
End();
|
||||
g.WithinEndPopupID = backup_within_end_popup_id;
|
||||
g.WithinEndChildID = backup_within_end_child_id;
|
||||
}
|
||||
|
||||
@@ -14025,7 +14113,7 @@ static void ImGui::NavProcessItem()
|
||||
const ImGuiID id = g.LastItemData.ID;
|
||||
const ImGuiItemFlags item_flags = g.LastItemData.ItemFlags;
|
||||
|
||||
// When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221, #8816)
|
||||
// When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221, #8816, #7994)
|
||||
ImRect nav_bb = g.LastItemData.NavRect;
|
||||
if (window->DC.NavIsScrollPushableX == false)
|
||||
{
|
||||
@@ -14428,13 +14516,13 @@ static void ImGui::NavUpdate()
|
||||
// FIXME-NAV: Now that keys are separated maybe we can get rid of NavInputSource?
|
||||
const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
||||
const ImGuiKey nav_gamepad_keys_to_change_source[] = { ImGuiKey_GamepadFaceRight, ImGuiKey_GamepadFaceLeft, ImGuiKey_GamepadFaceUp, ImGuiKey_GamepadFaceDown, ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown };
|
||||
if (nav_gamepad_active)
|
||||
if (nav_gamepad_active && g.NavInputSource != ImGuiInputSource_Gamepad)
|
||||
for (ImGuiKey key : nav_gamepad_keys_to_change_source)
|
||||
if (IsKeyDown(key))
|
||||
g.NavInputSource = ImGuiInputSource_Gamepad;
|
||||
const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
|
||||
const ImGuiKey nav_keyboard_keys_to_change_source[] = { ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow };
|
||||
if (nav_keyboard_active)
|
||||
if (nav_keyboard_active && g.NavInputSource != ImGuiInputSource_Keyboard)
|
||||
for (ImGuiKey key : nav_keyboard_keys_to_change_source)
|
||||
if (IsKeyDown(key))
|
||||
g.NavInputSource = ImGuiInputSource_Keyboard;
|
||||
@@ -15807,7 +15895,7 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop
|
||||
IM_ASSERT(viewport != NULL);
|
||||
ImRect bb = g.DragDropTargetRect;
|
||||
bb.Expand(-3.5f);
|
||||
RenderDragDropTargetRectEx(GetForegroundDrawList(viewport), bb);
|
||||
RenderDragDropTargetRectEx(GetForegroundDrawList(viewport), bb, g.Style.DragDropTargetRounding);
|
||||
}
|
||||
else if (draw_target_rect)
|
||||
{
|
||||
@@ -15838,16 +15926,16 @@ void ImGui::RenderDragDropTargetRectForItem(const ImRect& bb)
|
||||
bool push_clip_rect = !window->ClipRect.Contains(bb_display);
|
||||
if (push_clip_rect)
|
||||
window->DrawList->PushClipRectFullScreen();
|
||||
RenderDragDropTargetRectEx(window->DrawList, bb_display);
|
||||
RenderDragDropTargetRectEx(window->DrawList, bb_display, g.Style.DragDropTargetRounding);
|
||||
if (push_clip_rect)
|
||||
window->DrawList->PopClipRect();
|
||||
}
|
||||
|
||||
void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb)
|
||||
void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
draw_list->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTargetBg), g.Style.DragDropTargetRounding, 0);
|
||||
draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTarget), g.Style.DragDropTargetRounding, 0, g.Style.DragDropTargetBorderSize);
|
||||
draw_list->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTargetBg), rounding, 0);
|
||||
draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTarget), rounding, g.Style.DragDropTargetBorderSize);
|
||||
}
|
||||
|
||||
const ImGuiPayload* ImGui::GetDragDropPayload()
|
||||
@@ -16576,6 +16664,7 @@ void ImGuiPlatformIO::ClearRendererHandlers()
|
||||
Renderer_CreateWindow = Renderer_DestroyWindow = NULL;
|
||||
Renderer_SetWindowSize = NULL;
|
||||
Renderer_RenderWindow = Renderer_SwapBuffers = NULL;
|
||||
DrawCallback_ResetRenderState = DrawCallback_SetSamplerLinear = DrawCallback_SetSamplerNearest = NULL;
|
||||
}
|
||||
|
||||
ImGuiViewport* ImGui::GetMainViewport()
|
||||
@@ -16691,7 +16780,7 @@ static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImG
|
||||
|
||||
for (ImGuiViewportP* viewport_obstructing : g.Viewports)
|
||||
{
|
||||
if (viewport_obstructing == viewport_src || viewport_obstructing == viewport_dst)
|
||||
if (viewport_obstructing == viewport_src || viewport_obstructing == viewport_dst || !viewport_obstructing->PlatformWindowCreated)
|
||||
continue;
|
||||
if (viewport_obstructing->GetMainRect().Overlaps(window->Rect()))
|
||||
if (IsViewportAbove(viewport_obstructing, viewport_dst))
|
||||
@@ -17366,6 +17455,8 @@ void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_win
|
||||
|
||||
window->Viewport->Flags = viewport_flags;
|
||||
|
||||
window->Viewport->PlatformIconData = window->WindowClass.PlatformIconData;
|
||||
|
||||
// Update parent viewport ID
|
||||
// (the !IsFallbackWindow test mimic the one done in WindowSelectViewport())
|
||||
if (window->WindowClass.ParentViewportId != (ImGuiID)-1)
|
||||
@@ -18883,10 +18974,13 @@ static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node)
|
||||
node->WantHiddenTabBarToggle = false;
|
||||
|
||||
// Apply toggles at a single point of the frame (here!)
|
||||
const ImGuiDockNodeFlags prev_local_flags = node->LocalFlags;
|
||||
if (node->Windows.Size > 1)
|
||||
node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
|
||||
else if (node->WantHiddenTabBarToggle)
|
||||
node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar);
|
||||
if ((node->LocalFlags ^ prev_local_flags) & ImGuiDockNodeFlags_SavedFlagsMask_)
|
||||
MarkIniSettingsDirty(); // Bit flaky to only do this here. Perhaps compare node flags every frame? #9380
|
||||
node->WantHiddenTabBarToggle = false;
|
||||
|
||||
DockNodeUpdateVisibleFlag(node);
|
||||
@@ -21987,7 +22081,7 @@ void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list)
|
||||
draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding);
|
||||
ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y);
|
||||
ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y);
|
||||
draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, 2.0f);
|
||||
draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, 2.0f);
|
||||
draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding);
|
||||
ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y);
|
||||
draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_data->Label);
|
||||
@@ -22410,7 +22504,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
|
||||
|
||||
BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name);
|
||||
if (IsItemHovered())
|
||||
GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
|
||||
GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f);
|
||||
Indent();
|
||||
char buf[128];
|
||||
for (int rect_n = 0; rect_n < TRT_Count; rect_n++)
|
||||
@@ -22425,7 +22519,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
|
||||
ImFormatString(buf, IM_COUNTOF(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]);
|
||||
Selectable(buf);
|
||||
if (IsItemHovered())
|
||||
GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
|
||||
GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -22434,7 +22528,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
|
||||
ImFormatString(buf, IM_COUNTOF(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]);
|
||||
Selectable(buf);
|
||||
if (IsItemHovered())
|
||||
GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
|
||||
GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 2.0f);
|
||||
}
|
||||
}
|
||||
Unindent();
|
||||
@@ -22922,7 +23016,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
|
||||
ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n);
|
||||
ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255);
|
||||
float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f;
|
||||
draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness);
|
||||
draw_list->AddRect(r.Min, r.Max, col, 0.0f, thickness);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -23199,7 +23293,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con
|
||||
{
|
||||
ImDrawListFlags backup_flags = fg_draw_list->Flags;
|
||||
fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles.
|
||||
fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f);
|
||||
fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed);
|
||||
fg_draw_list->Flags = backup_flags;
|
||||
}
|
||||
}
|
||||
@@ -23227,7 +23321,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co
|
||||
for (int n = 0; n < 3; n++, idx_n++)
|
||||
vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos));
|
||||
if (show_mesh)
|
||||
out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); // In yellow: mesh triangles
|
||||
out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed); // In yellow: mesh triangles
|
||||
}
|
||||
// Draw bounding boxes
|
||||
if (show_aabb)
|
||||
@@ -23508,8 +23602,8 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label)
|
||||
{
|
||||
ImDrawList* draw_list = GetForegroundDrawList(tab_bar->Window);
|
||||
draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255));
|
||||
draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));
|
||||
draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));
|
||||
draw_list->AddLineV(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y, tab_bar->BarRect.Max.y, IM_COL32(0, 255, 0, 255));
|
||||
draw_list->AddLineV(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y, tab_bar->BarRect.Max.y, IM_COL32(0, 255, 0, 255));
|
||||
}
|
||||
if (open)
|
||||
{
|
||||
@@ -23871,8 +23965,8 @@ void ImGui::DebugDrawCursorPos(ImU32 col)
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiWindow* window = g.CurrentWindow;
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
window->DrawList->AddLine(ImVec2(pos.x, pos.y - 3.0f), ImVec2(pos.x, pos.y + 4.0f), col, 1.0f);
|
||||
window->DrawList->AddLine(ImVec2(pos.x - 3.0f, pos.y), ImVec2(pos.x + 4.0f, pos.y), col, 1.0f);
|
||||
window->DrawList->AddLineV(pos.x, pos.y - 3.0f, pos.y + 4.0f, col, 1.0f);
|
||||
window->DrawList->AddLineH(pos.x - 3.0f, pos.x + 4.0f, pos.y, col, 1.0f);
|
||||
}
|
||||
|
||||
// Draw a 10px wide rectangle around CurposPos.x using Line Y1/Y2 in current window's DrawList
|
||||
@@ -23883,9 +23977,9 @@ void ImGui::DebugDrawLineExtents(ImU32 col)
|
||||
float curr_x = window->DC.CursorPos.x;
|
||||
float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y);
|
||||
float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y);
|
||||
window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y1), ImVec2(curr_x + 5.0f, line_y1), col, 1.0f);
|
||||
window->DrawList->AddLine(ImVec2(curr_x - 0.5f, line_y1), ImVec2(curr_x - 0.5f, line_y2), col, 1.0f);
|
||||
window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y2), ImVec2(curr_x + 5.0f, line_y2), col, 1.0f);
|
||||
window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y1, col, 1.0f);
|
||||
window->DrawList->AddLineV(curr_x - 0.5f, line_y1, line_y2, col, 1.0f);
|
||||
window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y2, col, 1.0f);
|
||||
}
|
||||
|
||||
// Draw last item rect in ForegroundDrawList (so it is always visible)
|
||||
@@ -24255,7 +24349,7 @@ void ImGui::ShowFontSelector(const char* label)
|
||||
"- Load additional fonts with io.Fonts->AddFontXXX() functions.\n"
|
||||
"- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n"
|
||||
"- Read FAQ and docs/FONTS.md for more details.\n"
|
||||
"- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame().");
|
||||
"- Legacy backend: if you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame().");
|
||||
}
|
||||
#endif // #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS)
|
||||
|
||||
|
||||
Vendored
+80
-48
@@ -1,4 +1,4 @@
|
||||
// dear imgui, v1.92.7
|
||||
// dear imgui, v1.92.9 WIP
|
||||
// (headers)
|
||||
|
||||
// Help:
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
// Library Version
|
||||
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
|
||||
#define IMGUI_VERSION "1.92.7"
|
||||
#define IMGUI_VERSION_NUM 19270
|
||||
#define IMGUI_VERSION "1.92.9 WIP"
|
||||
#define IMGUI_VERSION_NUM 19282
|
||||
#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000
|
||||
#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198
|
||||
#define IMGUI_HAS_VIEWPORT // In 'docking' WIP branch.
|
||||
@@ -621,7 +621,8 @@ namespace ImGui
|
||||
IMGUI_API ImGuiID GetID(int int_id);
|
||||
|
||||
// Widgets: Text
|
||||
IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text.
|
||||
// - Note that all functions taking format strings in the API may be passed ("%s", text) or ("%.*s", text_len, text): which will automatically bypass the formatter.
|
||||
IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Practically equivalent to 'Text("%s", text)' but doesn't require null terminated string if 'text_end' is specified.
|
||||
IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // formatted text
|
||||
IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1);
|
||||
IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor();
|
||||
@@ -845,7 +846,7 @@ namespace ImGui
|
||||
|
||||
// Popups, Modals
|
||||
// - They block normal mouse hovering detection (and therefore most mouse interactions) behind them.
|
||||
// - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE.
|
||||
// - If not modal: they can be closed by clicking anywhere outside them, or by pressing Escape (call 'Shortcut(ImGuiKey_Escape)' to claim a higher-priority shortcut).
|
||||
// - Their visibility state (~bool) is held internally instead of being held by the programmer as we are used to with regular Begin*() calls.
|
||||
// - The 3 properties above are related: we need to retain popup visibility state in the library because popups may be closed as any time.
|
||||
// - You can bypass the hovering restriction by using ImGuiHoveredFlags_AllowWhenBlockedByPopup when calling IsItemHovered() or IsWindowHovered().
|
||||
@@ -1128,10 +1129,11 @@ namespace ImGui
|
||||
// Inputs Utilities: Key/Input Ownership [BETA]
|
||||
// - One common use case would be to allow your items to disable standard inputs behaviors such
|
||||
// as Tab or Alt key handling, Mouse Wheel scrolling, etc.
|
||||
// e.g. Button(...); SetItemKeyOwner(ImGuiKey_MouseWheelY); to make hovering/activating a button disable wheel for scrolling.
|
||||
// e.g. `Button(...); if (SetItemKeyOwner(ImGuiKey_MouseWheelY)) { ... }` to make hovering/activating a button disable wheel for scrolling.
|
||||
// - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them.
|
||||
// - The return value of SetItemKeyOwner() says if ownership has been requested for the item, which is a shortcut to calling yet non-public TestKeyOwner() function.
|
||||
// - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version.
|
||||
IMGUI_API void SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'.
|
||||
IMGUI_API bool SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Return true when ownership has been set. Roughly equivalent to 'if (TestKeyOwner(key, GetItemID()) && (IsItemHovered() || IsItemActive())) { SetKeyOwner(key, GetItemID());'.
|
||||
|
||||
// Inputs Utilities: Mouse
|
||||
// - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right.
|
||||
@@ -1376,7 +1378,7 @@ enum ImGuiTreeNodeFlags_
|
||||
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
ImGuiTreeNodeFlags_NavLeftJumpsBackHere = ImGuiTreeNodeFlags_NavLeftJumpsToParent, // Renamed in 1.92.0
|
||||
ImGuiTreeNodeFlags_SpanTextWidth = ImGuiTreeNodeFlags_SpanLabelWidth, // Renamed in 1.90.7
|
||||
//ImGuiTreeNodeFlags_SpanTextWidth = ImGuiTreeNodeFlags_SpanLabelWidth, // Renamed in 1.90.7
|
||||
//ImGuiTreeNodeFlags_AllowItemOverlap = ImGuiTreeNodeFlags_AllowOverlap, // Renamed in 1.89.7
|
||||
#endif
|
||||
};
|
||||
@@ -1448,7 +1450,7 @@ enum ImGuiTabBarFlags_
|
||||
ImGuiTabBarFlags_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab
|
||||
|
||||
// Fitting/Resize policy
|
||||
ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling buttons.
|
||||
ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling. Setting TabMinWidthShrink to FLT_MAX makes this behave like ImGuiTabBarFlags_FittingPolicyScroll.
|
||||
ImGuiTabBarFlags_FittingPolicyShrink = 1 << 8, // Shrink down tabs when they don't fit
|
||||
ImGuiTabBarFlags_FittingPolicyScroll = 1 << 9, // Enable scrolling buttons when tabs don't fit
|
||||
ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyMixed | ImGuiTabBarFlags_FittingPolicyShrink | ImGuiTabBarFlags_FittingPolicyScroll,
|
||||
@@ -1813,7 +1815,7 @@ enum ImGuiBackendFlags_
|
||||
ImGuiBackendFlags_RendererHasViewports = 1 << 10, // Backend Renderer supports multiple viewports.
|
||||
ImGuiBackendFlags_PlatformHasViewports = 1 << 11, // Backend Platform supports multiple viewports.
|
||||
ImGuiBackendFlags_HasMouseHoveredViewport=1 << 12, // Backend Platform supports calling io.AddMouseViewportEvent() with the viewport under the mouse. IF POSSIBLE, ignore viewports with the ImGuiViewportFlags_NoInputs flag (Win32 backend, GLFW 3.30+ backend can do this, SDL backend cannot). If this cannot be done, Dear ImGui needs to use a flawed heuristic to find the viewport under.
|
||||
ImGuiBackendFlags_HasParentViewport = 1 << 13, // Backend Platform supports honoring viewport->ParentViewport/ParentViewportId value, by applying the corresponding parent/child relation at the Platform level.
|
||||
ImGuiBackendFlags_HasParentViewport = 1 << 13, // Backend Platform supports honoring viewport->ParentViewport/ParentViewportId value, by applying the corresponding parent/child relationship at the Platform level. Child windows always appear in front of their parent window.
|
||||
};
|
||||
|
||||
// Enumeration for PushStyleColor() / PopStyleColor()
|
||||
@@ -1838,6 +1840,7 @@ enum ImGuiCol_
|
||||
ImGuiCol_ScrollbarGrabHovered,
|
||||
ImGuiCol_ScrollbarGrabActive,
|
||||
ImGuiCol_CheckMark, // Checkbox tick and RadioButton circle
|
||||
ImGuiCol_CheckboxSelectedBg, // Checkbox background when Selected, otherwise use FrameBg
|
||||
ImGuiCol_SliderGrab,
|
||||
ImGuiCol_SliderGrabActive,
|
||||
ImGuiCol_Button,
|
||||
@@ -1937,6 +1940,7 @@ enum ImGuiStyleVar_
|
||||
ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign
|
||||
ImGuiStyleVar_TreeLinesSize, // float TreeLinesSize
|
||||
ImGuiStyleVar_TreeLinesRounding, // float TreeLinesRounding
|
||||
ImGuiStyleVar_DragDropTargetRounding, // float DragDropTargetRounding
|
||||
ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign
|
||||
ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign
|
||||
ImGuiStyleVar_SeparatorSize, // float SeparatorSize
|
||||
@@ -2405,7 +2409,7 @@ struct ImGuiStyle
|
||||
float TabBorderSize; // Thickness of border around tabs.
|
||||
float TabMinWidthBase; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected.
|
||||
float TabMinWidthShrink; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy.
|
||||
float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.
|
||||
float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never shrink, will behave like ImGuiTabBarFlags_FittingPolicyScroll.
|
||||
float TabCloseButtonMinWidthUnselected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected.
|
||||
float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus.
|
||||
float TabBarOverlineSize; // Thickness of tab-bar overline, which highlights the selected tab-bar.
|
||||
@@ -2414,14 +2418,15 @@ struct ImGuiStyle
|
||||
ImGuiTreeNodeFlags TreeLinesFlags; // Default way to draw lines connecting TreeNode hierarchy. ImGuiTreeNodeFlags_DrawLinesNone or ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes.
|
||||
float TreeLinesSize; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines.
|
||||
float TreeLinesRounding; // Radius of lines connecting child nodes to the vertical line.
|
||||
float DragDropTargetRounding; // Radius of the drag and drop target frame.
|
||||
float DragDropTargetRounding; // Radius of the drag and drop target frame. When <0.0f: use FrameRounding.
|
||||
float DragDropTargetBorderSize; // Thickness of the drag and drop target border.
|
||||
float DragDropTargetPadding; // Size to expand the drag and drop target from actual target item size.
|
||||
float ColorMarkerSize; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers.
|
||||
ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
|
||||
ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered).
|
||||
ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.
|
||||
float SeparatorSize; // Thickness of border in Separator()
|
||||
float InputTextCursorSize; // Thickness of cursor/caret in InputText().
|
||||
float SeparatorSize; // Thickness of border in Separator(). Must be >= 1.0f.
|
||||
float SeparatorTextBorderSize; // Thickness of border in SeparatorText()
|
||||
ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).
|
||||
ImVec2 SeparatorTextPadding; // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y.
|
||||
@@ -2453,7 +2458,7 @@ struct ImGuiStyle
|
||||
|
||||
// Functions
|
||||
IMGUI_API ImGuiStyle();
|
||||
IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts.
|
||||
IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. See comments in definition. Consider not calling this if your initial scale factor if <1.0.
|
||||
|
||||
// Obsolete names
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
@@ -2521,10 +2526,11 @@ struct ImGuiIO
|
||||
bool ConfigDockingTransparentPayload;// = false // [BETA] Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge.
|
||||
|
||||
// Viewport options (when ImGuiConfigFlags_ViewportsEnable is set)
|
||||
// (sorry for the amount of "NoXXXX" flags, which may be harder to reason about! may rework someday)
|
||||
bool ConfigViewportsNoAutoMerge; // = false; // Set to make all floating imgui windows always create their own viewport. Otherwise, they are merged into the main host viewports when overlapping it. May also set ImGuiViewportFlags_NoAutoMerge on individual viewport.
|
||||
bool ConfigViewportsNoTaskBarIcon; // = false // Disable default OS task bar icon flag for secondary viewports. When a viewport doesn't want a task bar icon, ImGuiViewportFlags_NoTaskBarIcon will be set on it.
|
||||
bool ConfigViewportsNoDecoration; // = true // Disable default OS window decoration flag for secondary viewports. When a viewport doesn't want window decorations, ImGuiViewportFlags_NoDecoration will be set on it. Enabling decoration can create subsequent issues at OS levels (e.g. minimum window size).
|
||||
bool ConfigViewportsNoDefaultParent; // = true // When false: set secondary viewports' ParentViewportId to main viewport ID by default. Expects the platform backend to setup a parent/child relationship between the OS windows based on this value. Some backend may ignore this. Set to true if you want viewports to automatically be parent of main viewport, otherwise all viewports will be top-level OS windows.
|
||||
bool ConfigViewportsNoDefaultParent; // = true // Disable setting OS window parent to main viewport by default. The platform backend is expected to honor `viewport->ParentViewportID` to setup a parent/child relationship between the OS windows (supported if ImGuiBackendFlags_HasParentViewport is set). When parented: child windows always appear in front of their parent. Set to false if you want viewports to automatically be parent of main viewport, otherwise all viewports will be top-level OS windows. Parent/child relationship may be set on a per-window basis using ImGuiWindowClass.
|
||||
bool ConfigViewportsPlatformFocusSetsImGuiFocus;//= true // When a platform window is focused (e.g. using Alt+Tab, clicking Platform Title Bar), apply corresponding focus on imgui windows (may clear focus/active id from imgui windows location in other platform windows). In principle this is better enabled but we provide an opt-out, because some Linux window managers tend to eagerly focus windows (e.g. on mouse hover, or even a simple window pos/size change).
|
||||
|
||||
// DPI/Scaling options
|
||||
@@ -2719,7 +2725,7 @@ struct ImGuiIO
|
||||
//void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning.
|
||||
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
float FontGlobalScale; // Moved io.FontGlobalScale to style.FontScaleMain in 1.92 (June 2025)
|
||||
float FontGlobalScale; // Moved io.FontGlobalScale to style.FontScaleMain in 1.92.0 (June 2025)
|
||||
|
||||
// Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO.
|
||||
// As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete).
|
||||
@@ -2752,7 +2758,7 @@ struct ImGuiInputTextCallbackData
|
||||
ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only
|
||||
ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only
|
||||
void* UserData; // What user passed to InputText() // Read-only
|
||||
ImGuiID ID; // Widget ID // Read-only
|
||||
ImGuiID ID; // Widget ID // Read-only
|
||||
|
||||
// Arguments for the different callback events
|
||||
// - During Resize callback, Buf will be same as your input buffer.
|
||||
@@ -2766,9 +2772,9 @@ struct ImGuiInputTextCallbackData
|
||||
char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer!
|
||||
int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length()
|
||||
int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land: == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1
|
||||
int CursorPos; // // Read-write // [Completion,History,Always]
|
||||
int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection
|
||||
int SelectionEnd; // // Read-write // [Completion,History,Always]
|
||||
int CursorPos; // // Read-write // [Completion,History,Always,CharFilter]
|
||||
int SelectionStart; // // Read-write // [Completion,History,Always,CharFilter] == to SelectionEnd when no selection
|
||||
int SelectionEnd; // // Read-write // [Completion,History,Always,CharFilter]
|
||||
|
||||
// Helper functions for text manipulation.
|
||||
// Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection.
|
||||
@@ -2796,7 +2802,7 @@ struct ImGuiSizeCallbackData
|
||||
// before we stabilize Docking features. Please be mindful if using this.
|
||||
// Provide hints:
|
||||
// - To the platform backend via altered viewport flags (enable/disable OS decoration, OS task bar icons, etc.)
|
||||
// - To the platform backend for OS level parent/child relationships of viewport.
|
||||
// - To the platform backend for OS level parent/child relationships of viewport (otherwise: default is configured via io.ConfigViewportsNoDefaultParent)
|
||||
// - To the docking system for various options and filtering.
|
||||
struct ImGuiWindowClass
|
||||
{
|
||||
@@ -2809,6 +2815,7 @@ struct ImGuiWindowClass
|
||||
ImGuiDockNodeFlags DockNodeFlagsOverrideSet; // [EXPERIMENTAL] Dock node flags to set when a window of this class is hosted by a dock node (it doesn't have to be selected!)
|
||||
bool DockingAlwaysTabBar; // Set to true to enforce single floating windows of this class always having their own docking node (equivalent of setting the global io.ConfigDockingAlwaysTabBar)
|
||||
bool DockingAllowUnclassed; // Set to true to allow windows of this class to be docked/merged with an unclassed window. // FIXME-DOCK: Move to DockNodeFlags override?
|
||||
void* PlatformIconData; // [EXPERIMENTAL] Pass opaque data for Platform backend to handle.
|
||||
|
||||
ImGuiWindowClass() { memset((void*)this, 0, sizeof(*this)); ParentViewportId = (ImGuiID)-1; DockingAllowUnclassed = true; }
|
||||
};
|
||||
@@ -3298,12 +3305,6 @@ typedef unsigned short ImDrawIdx; // Default: 16-bit (for maximum compatibilit
|
||||
typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd);
|
||||
#endif
|
||||
|
||||
// Special Draw callback value to request renderer backend to reset the graphics/render state.
|
||||
// The renderer backend needs to handle this special value, otherwise it will crash trying to call a function at this address.
|
||||
// This is useful, for example, if you submitted callbacks which you know have altered the render state and you want it to be restored.
|
||||
// Render state is not reset by default because they are many perfectly useful way of altering render state (e.g. changing shader/blending settings before an Image call).
|
||||
#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8)
|
||||
|
||||
// Typically, 1 command = 1 GPU draw call (unless command is a callback)
|
||||
// - VtxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled,
|
||||
// this fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices.
|
||||
@@ -3377,23 +3378,30 @@ struct ImDrawListSplitter
|
||||
};
|
||||
|
||||
// Flags for ImDrawList functions
|
||||
// (Legacy: bit 0 must always correspond to ImDrawFlags_Closed to be backward compatible with old API using a bool. Bits 1..3 must be unused)
|
||||
enum ImDrawFlags_
|
||||
{
|
||||
ImDrawFlags_None = 0,
|
||||
ImDrawFlags_Closed = 1 << 0, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason)
|
||||
ImDrawFlags_RoundCornersTopLeft = 1 << 4, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-left corner only (when rounding > 0.0f, we default to all corners). Was 0x01.
|
||||
ImDrawFlags_RoundCornersTopRight = 1 << 5, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-right corner only (when rounding > 0.0f, we default to all corners). Was 0x02.
|
||||
ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-left corner only (when rounding > 0.0f, we default to all corners). Was 0x04.
|
||||
ImDrawFlags_RoundCornersBottomRight = 1 << 7, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-right corner only (when rounding > 0.0f, we default to all corners). Wax 0x08.
|
||||
ImDrawFlags_RoundCornersNone = 1 << 8, // AddRect(), AddRectFilled(), PathRect(): disable rounding on all corners (when rounding > 0.0f). This is NOT zero, NOT an implicit flag!
|
||||
|
||||
// Rounding for AddRect(), AddRectFilled(), PathRect()
|
||||
// - When not specified, we defaults to ImDrawFlags_RoundCornersAll! So you only need to use those flags if you want another configuration.
|
||||
ImDrawFlags_RoundCornersTopLeft = 1 << 4, // Round top-left corner only (when rounding > 0.0f, we default to all corners).
|
||||
ImDrawFlags_RoundCornersTopRight = 1 << 5, // Round top-right corner only (when rounding > 0.0f, we default to all corners).
|
||||
ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // Round bottom-left corner only (when rounding > 0.0f, we default to all corners).
|
||||
ImDrawFlags_RoundCornersBottomRight = 1 << 7, // Round bottom-right corner only (when rounding > 0.0f, we default to all corners).
|
||||
ImDrawFlags_RoundCornersNone = 1 << 8, // Disable rounding even if `float rounding > 0.0f`. This is NOT zero, NOT an implicit flag!
|
||||
ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, // (Default!!)
|
||||
ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified!
|
||||
ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight,
|
||||
ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight,
|
||||
ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft,
|
||||
ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight,
|
||||
ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight,
|
||||
ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified.
|
||||
ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone,
|
||||
|
||||
// Stroke options
|
||||
ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed.
|
||||
//ImDrawFlags_Closed = 1 << 0, // Prior to 1.92.8 (May 2026), ImDrawFlags_Closed was guaranteed to be == 1<<0 == 1 for legacy compatibility reason. Hardcoded use of 1 or true should be replaced.
|
||||
|
||||
ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F,
|
||||
};
|
||||
|
||||
// Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly.
|
||||
@@ -3405,6 +3413,7 @@ enum ImDrawListFlags_
|
||||
ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear filtering (NOT point/nearest filtering).
|
||||
ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles).
|
||||
ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled.
|
||||
ImDrawListFlags_TextNoPixelSnap = 1 << 4, // Disable automatically snapping AddText() calls to pixel boundaries.
|
||||
};
|
||||
|
||||
// Draw command list
|
||||
@@ -3459,7 +3468,9 @@ struct ImDrawList
|
||||
// In future versions we will use textures to provide cheaper and higher-quality circles.
|
||||
// Use AddNgon() and AddNgonFilled() functions if you need to guarantee a specific number of sides.
|
||||
IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f);
|
||||
IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); // a: upper-left, b: lower-right (== upper-left + size)
|
||||
IMGUI_API void AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness = 1.0f);
|
||||
IMGUI_API void AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness = 1.0f);
|
||||
IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size)
|
||||
IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size)
|
||||
IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left);
|
||||
IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f);
|
||||
@@ -3480,7 +3491,7 @@ struct ImDrawList
|
||||
// General polygon
|
||||
// - Only simple polygons are supported by filling functions (no self-intersections, no holes).
|
||||
// - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library.
|
||||
IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness);
|
||||
IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0);
|
||||
IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col);
|
||||
IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col);
|
||||
|
||||
@@ -3500,7 +3511,7 @@ struct ImDrawList
|
||||
inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); }
|
||||
inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; }
|
||||
inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; }
|
||||
inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; }
|
||||
inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; }
|
||||
IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0);
|
||||
IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle
|
||||
IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse
|
||||
@@ -3510,14 +3521,15 @@ struct ImDrawList
|
||||
|
||||
// Advanced: Draw Callbacks
|
||||
// - May be used to alter render state (change sampler, blending, current shader). May be used to emit custom rendering commands (difficult to do correctly, but possible).
|
||||
// - Use special ImDrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default.
|
||||
// - Use special GetPlatformIO().DrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default.
|
||||
// - See other standard callbacks in GetPlatformIO(), which may or not be supported by your backend.
|
||||
// - Your rendering loop must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. All standard backends are honoring this.
|
||||
// - For some backends, the callback may access selected render-states exposed by the backend in a ImGui_ImplXXXX_RenderState structure pointed to by platform_io.Renderer_RenderState.
|
||||
// - IMPORTANT: please be mindful of the different level of indirection between using size==0 (copying argument) and using size>0 (copying pointed data into a buffer).
|
||||
// - If userdata_size == 0: we copy/store the 'userdata' argument as-is. It will be available unmodified in ImDrawCmd::UserCallbackData during render.
|
||||
// - If userdata_size > 0, we copy/store 'userdata_size' bytes pointed to by 'userdata'. We store them in a buffer stored inside the drawlist. ImDrawCmd::UserCallbackData will point inside that buffer so you have to retrieve data from there. Your callback may need to use ImDrawCmd::UserCallbackDataSize if you expect dynamically-sized data.
|
||||
// - Support for userdata_size > 0 was added in v1.91.4, October 2024. So earlier code always only allowed to copy/store a simple void*.
|
||||
IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size = 0);
|
||||
IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata = NULL, size_t userdata_size = 0);
|
||||
|
||||
// Advanced: Miscellaneous
|
||||
IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible
|
||||
@@ -3547,8 +3559,15 @@ struct ImDrawList
|
||||
|
||||
// Obsolete names
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
inline void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) { AddRect(p_min, p_max, col, rounding, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED.
|
||||
inline void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) { AddPolyline(points, num_points, col, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED.
|
||||
inline void PathStroke(ImU32 col, ImDrawFlags flags, float thickness) { PathStroke(col, thickness, flags); } // OBSOLETED in 1.92.8: NEW FUNCTION SIGNATURE HAS 'thickness' AND 'flags' SWAPPED.
|
||||
inline void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.0
|
||||
inline void PopTextureID() { PopTexture(); } // RENAMED in 1.92.0
|
||||
#else
|
||||
void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding /*= 0.0f*/, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete;
|
||||
void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) = delete;
|
||||
inline void PathStroke(ImU32 col, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete;
|
||||
#endif
|
||||
//inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024)
|
||||
//inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024)
|
||||
@@ -3660,6 +3679,7 @@ struct ImTextureData
|
||||
bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame.
|
||||
|
||||
// Functions
|
||||
// - If GetPixels() functions asserts while being called by your render loop, it could be caused by calling ImFontAtlas::Clear()/ClearFonts()?
|
||||
ImTextureData() { memset((void*)this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; }
|
||||
~ImTextureData() { DestroyPixels(); }
|
||||
IMGUI_API void Create(ImTextureFormat format, int w, int h);
|
||||
@@ -3813,15 +3833,17 @@ struct ImFontAtlas
|
||||
IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed.
|
||||
IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp.
|
||||
IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter.
|
||||
IMGUI_API void RemoveFont(ImFont* font);
|
||||
|
||||
IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures).
|
||||
IMGUI_API void CompactCache(); // Compact cached glyphs and texture.
|
||||
IMGUI_API void RemoveFont(ImFont* font); // Remove a font
|
||||
IMGUI_API void CompactCache(); // Compact cached glyphs and texture.
|
||||
IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime.
|
||||
|
||||
// As we are transitioning toward a new font system, we expect to obsolete those soon:
|
||||
// Clearing the atlas/fonts has little use nowadays, unless you want to batch remove all fonts.
|
||||
// - Since 1.92, you can call ClearFonts() mid-frame, if you load new fonts afterwards.
|
||||
// - As we are transitioning toward our new font system the semantic for those functions gets increasingly misleading and are often a source of issues.
|
||||
// TL;DR; most likely, don't use any of those functions. We expect to obsolete/rework them.
|
||||
IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame!
|
||||
IMGUI_API void ClearFonts(); // Clear input+output font data/glyphs. New fonts and textures will be recreated afterwards.
|
||||
IMGUI_API void ClearInputData(); // [OBSOLETE] Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts.
|
||||
IMGUI_API void ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates).
|
||||
IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory.
|
||||
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
@@ -3993,6 +4015,7 @@ enum ImFontFlags_
|
||||
ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value.
|
||||
ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs.
|
||||
ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display.
|
||||
ImFontFlags_ImplicitRefSize = 1 << 4, // [Internal] Reference size was not set explicitly.
|
||||
};
|
||||
|
||||
// Font runtime data and rendering
|
||||
@@ -4038,7 +4061,7 @@ struct ImFont
|
||||
IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL);
|
||||
IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, ImDrawTextFlags flags = 0);
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(LegacySize * scale, text, text_end, wrap_width); }
|
||||
inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(LegacySize * scale, text, text_end, wrap_width); } // Obsoleted old name in 1.92.0. Note how `scale` was to `size`.
|
||||
#endif
|
||||
|
||||
// [Internal] Don't use!
|
||||
@@ -4121,6 +4144,7 @@ struct ImGuiViewport
|
||||
// The library never uses those fields, they are merely storage to facilitate backend implementation.
|
||||
void* RendererUserData; // void* to hold custom data structure for the renderer (e.g. swap chain, framebuffers etc.). generally set by your Renderer_CreateWindow function.
|
||||
void* PlatformUserData; // void* to hold custom data structure for the OS / platform (e.g. windowing info, render context). generally set by your Platform_CreateWindow function.
|
||||
void* PlatformIconData; // void* to hold custom data structure for the OS / platform to specify an icon. Currently unused for exposed to allow experiments.
|
||||
void* PlatformHandle; // void* to hold higher-level, platform window handle (e.g. HWND for Win32 backend, Uint32 WindowID for SDL, GLFWWindow* for GLFW), for FindViewportByPlatformHandle().
|
||||
void* PlatformHandleRaw; // void* to hold lower-level, platform-native window handle (always HWND on Win32 platform, unused for other platforms).
|
||||
bool PlatformWindowCreated; // Platform window has been created (Platform_CreateWindow() has been called). This is false during the first frame where a viewport is being created.
|
||||
@@ -4228,6 +4252,12 @@ struct ImGuiPlatformIO
|
||||
// Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure.
|
||||
void* Renderer_RenderState;
|
||||
|
||||
// Standard draw callbacks provided by renderer backend.
|
||||
ImDrawCallback DrawCallback_ResetRenderState; // Request to reset the graphics/render state.
|
||||
ImDrawCallback DrawCallback_SetSamplerLinear; // Request backend to set texture sampling to Linear.
|
||||
ImDrawCallback DrawCallback_SetSamplerNearest; // Request backend to set texture sampling to Nearest/Point.
|
||||
//ImDrawCallback DrawCallback_SetSamplerCustom; // Request backend to set texture sampling using Backend Specific data.
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Input - Interface with Platform & Renderer backends for Multi-Viewport support
|
||||
//------------------------------------------------------------------
|
||||
@@ -4421,6 +4451,8 @@ namespace ImGui
|
||||
//static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42
|
||||
}
|
||||
|
||||
#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) // OBSOLETED in 1.92.8: Use ImGui::GetPlatformIO().DrawCallback_ResetRenderState
|
||||
|
||||
//-- OBSOLETED in 1.92.0: ImFontAtlasCustomRect becomes ImTextureRect
|
||||
// - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y
|
||||
// - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h
|
||||
|
||||
+217
-92
@@ -1,4 +1,4 @@
|
||||
// dear imgui, v1.92.7
|
||||
// dear imgui, v1.92.9 WIP
|
||||
// (demo code)
|
||||
|
||||
// Help:
|
||||
@@ -73,6 +73,7 @@ Index of this file:
|
||||
// [SECTION] Demo Window / ShowDemoWindow()
|
||||
// [SECTION] DemoWindowMenuBar()
|
||||
// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos)
|
||||
// [SECTION] Helpers: ExampleImageViewer
|
||||
// [SECTION] DemoWindowWidgetsBasic()
|
||||
// [SECTION] DemoWindowWidgetsBullets()
|
||||
// [SECTION] DemoWindowWidgetsCollapsingHeaders()
|
||||
@@ -108,6 +109,7 @@ Index of this file:
|
||||
// [SECTION] User Guide / ShowUserGuide()
|
||||
// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar()
|
||||
// [SECTION] Example App: Debug Console / ShowExampleAppConsole()
|
||||
// [SECTION] Example App: Image Viewer / ShowExampleAppImageViewer()
|
||||
// [SECTION] Example App: Debug Log / ShowExampleAppLog()
|
||||
// [SECTION] Example App: Simple Layout / ShowExampleAppLayout()
|
||||
// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor()
|
||||
@@ -240,6 +242,7 @@ static void ShowExampleAppConsole(bool* p_open);
|
||||
static void ShowExampleAppCustomRendering(bool* p_open);
|
||||
static void ShowExampleAppDockSpace(bool* p_open);
|
||||
static void ShowExampleAppDocuments(bool* p_open);
|
||||
static void ShowExampleAppImageViewer(bool* p_open);
|
||||
static void ShowExampleAppLog(bool* p_open);
|
||||
static void ShowExampleAppLayout(bool* p_open);
|
||||
static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data);
|
||||
@@ -321,6 +324,7 @@ struct ImGuiDemoWindowData
|
||||
bool ShowAppCustomRendering = false;
|
||||
bool ShowAppDocuments = false;
|
||||
bool ShowAppDockSpace = false;
|
||||
bool ShowAppImageViewer = false;
|
||||
bool ShowAppLog = false;
|
||||
bool ShowAppLayout = false;
|
||||
bool ShowAppPropertyEditor = false;
|
||||
@@ -367,6 +371,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
|
||||
if (demo_data.ShowAppAssetsBrowser) { ShowExampleAppAssetsBrowser(&demo_data.ShowAppAssetsBrowser); }
|
||||
if (demo_data.ShowAppConsole) { ShowExampleAppConsole(&demo_data.ShowAppConsole); }
|
||||
if (demo_data.ShowAppCustomRendering) { ShowExampleAppCustomRendering(&demo_data.ShowAppCustomRendering); }
|
||||
if (demo_data.ShowAppImageViewer) { ShowExampleAppImageViewer(&demo_data.ShowAppImageViewer); }
|
||||
if (demo_data.ShowAppLog) { ShowExampleAppLog(&demo_data.ShowAppLog); }
|
||||
if (demo_data.ShowAppLayout) { ShowExampleAppLayout(&demo_data.ShowAppLayout); }
|
||||
if (demo_data.ShowAppPropertyEditor) { ShowExampleAppPropertyEditor(&demo_data.ShowAppPropertyEditor, &demo_data); }
|
||||
@@ -748,6 +753,7 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data)
|
||||
ImGui::MenuItem("Custom rendering", NULL, &demo_data->ShowAppCustomRendering);
|
||||
ImGui::MenuItem("Documents", NULL, &demo_data->ShowAppDocuments);
|
||||
ImGui::MenuItem("Dockspace", NULL, &demo_data->ShowAppDockSpace);
|
||||
ImGui::MenuItem("Image Viewer", NULL, &demo_data->ShowAppImageViewer);
|
||||
ImGui::MenuItem("Log", NULL, &demo_data->ShowAppLog);
|
||||
ImGui::MenuItem("Property editor", NULL, &demo_data->ShowAppPropertyEditor);
|
||||
ImGui::MenuItem("Simple layout", NULL, &demo_data->ShowAppLayout);
|
||||
@@ -779,7 +785,7 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data)
|
||||
ImGui::Checkbox("Highlight ID Conflicts", &io.ConfigDebugHighlightIdConflicts);
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Checkbox("Assert on error recovery", &io.ConfigErrorRecoveryEnableAssert);
|
||||
ImGui::TextDisabled("(see Demo->Configuration for details & more)");
|
||||
ImGui::TextDisabled("(see Demo->Configuration for more)");
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::MenuItem("Debug Log", NULL, &demo_data->ShowDebugLog, has_debug_tools);
|
||||
@@ -896,6 +902,87 @@ static ExampleTreeNode* ExampleTree_CreateDemoTree()
|
||||
return node_L0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// [SECTION] Helpers: ExampleImageViewer
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
struct ExampleImageViewerData
|
||||
{
|
||||
ImU32 ImageBgColor = IM_COL32(100, 100, 100, 255);
|
||||
ImU32 GridColor = IM_COL32(255, 255, 255, 100);
|
||||
bool GridEnabled = true;
|
||||
bool ViewReset = true;
|
||||
ImVec2 ViewOffset; // in image space
|
||||
float Zoom = 10.0f;
|
||||
float ZoomMin = 1.0f;
|
||||
float ZoomMax = 10000.0f;
|
||||
};
|
||||
|
||||
static void ExampleImageViewer_DrawOptions(ExampleImageViewerData* data)
|
||||
{
|
||||
ImGui::SetNextItemShortcut(ImGuiKey_G, ImGuiInputFlags_Tooltip); // | ImGuiInputFlags_RouteGlobal
|
||||
ImGui::Checkbox("Grid", &data->GridEnabled);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
|
||||
float zoom_100 = data->Zoom * 100.0f;
|
||||
if (ImGui::DragFloat("##Zoom", &zoom_100, 5.0f, data->ZoomMin * 100.0f, data->ZoomMax * 100.0f, "%.0f%%", ImGuiSliderFlags_AlwaysClamp))
|
||||
data->Zoom = zoom_100 / 100.0f;
|
||||
}
|
||||
|
||||
static void ExampleImageViewer_DrawCanvas(ExampleImageViewerData* data, ImVec2 canvas_size, ImTextureRef image_tex_ref, int image_w, int image_h)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
IM_ASSERT(canvas_size.x >= 0.0f && canvas_size.y >= 0.0f);
|
||||
|
||||
// Layout canvas
|
||||
ImGui::InvisibleButton("##Canvas", canvas_size);
|
||||
ImVec2 canvas_min = ImGui::GetItemRectMin();
|
||||
ImVec2 canvas_max = ImGui::GetItemRectMax();
|
||||
|
||||
if (data->ViewReset)
|
||||
data->ViewOffset = ImVec2((canvas_size.x * 0.5f / data->Zoom) - 0.5f, (canvas_size.y * 0.5f / data->Zoom) - 0.5f); // Add half a pixel padding
|
||||
data->ViewReset = false;
|
||||
|
||||
// Handle inputs
|
||||
if (ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY))
|
||||
if (io.MouseWheel != 0.0f)
|
||||
data->Zoom = IM_CLAMP(data->Zoom * (1.0f + io.MouseWheel * 0.10f), data->ZoomMin, data->ZoomMax);
|
||||
float zoom = data->Zoom; // (float)(int)ViewZoom;
|
||||
if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0))
|
||||
{
|
||||
data->ViewOffset.x -= io.MouseDelta.x / zoom;
|
||||
data->ViewOffset.y -= io.MouseDelta.y / zoom;
|
||||
}
|
||||
|
||||
// Display image
|
||||
ImVec2 image_min, image_max;
|
||||
image_min.x = (float)(int)((canvas_min.x - (data->ViewOffset.x * zoom)) + (canvas_size.x * 0.5f));
|
||||
image_min.y = (float)(int)((canvas_min.y - (data->ViewOffset.y * zoom)) + (canvas_size.y * 0.5f));
|
||||
image_max.x = (float)(int)(image_min.x + image_w * zoom);
|
||||
image_max.y = (float)(int)(image_min.y + image_h * zoom);
|
||||
draw_list->AddRect(ImVec2(canvas_min.x - 1.0f, canvas_min.y - 1.0f), ImVec2(canvas_max.x + 1.0f, canvas_max.y + 1.0f), IM_COL32(255, 255, 255, 255));
|
||||
draw_list->PushClipRect(canvas_min, canvas_max, true);
|
||||
draw_list->AddRectFilled(image_min, image_max, data->ImageBgColor);
|
||||
if (platform_io.DrawCallback_SetSamplerNearest != NULL)
|
||||
draw_list->AddCallback(platform_io.DrawCallback_SetSamplerNearest);
|
||||
draw_list->AddImage(image_tex_ref, image_min, image_max);
|
||||
if (platform_io.DrawCallback_SetSamplerLinear != NULL)
|
||||
draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear);
|
||||
|
||||
// Display grid lines for visible pixels
|
||||
if (data->GridEnabled && zoom > 6.0f)
|
||||
{
|
||||
const float step = (float)zoom;
|
||||
for (int px = (int)((canvas_min.x - image_min.x) / step); px <= (int)((canvas_max.x - image_min.x) / step); px++)
|
||||
draw_list->AddLineV(image_min.x + px * step, canvas_min.y, canvas_max.y, data->GridColor, 1.0f);
|
||||
for (int py = (int)((canvas_min.y - image_min.y) / step); py <= (int)((canvas_max.y - image_min.y) / step); py++)
|
||||
draw_list->AddLineH(canvas_min.x, canvas_max.x, image_min.y + py * step, data->GridColor, 1.0f);
|
||||
}
|
||||
draw_list->PopClipRect();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// [SECTION] DemoWindowWidgetsBasic()
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -1873,40 +1960,29 @@ static void DemoWindowWidgetsImages()
|
||||
// - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
|
||||
|
||||
// Grab the current texture identifier used by the font atlas.
|
||||
ImTextureRef my_tex_id = io.Fonts->TexRef;
|
||||
ImFontAtlas* atlas = io.Fonts;
|
||||
ImTextureRef my_tex_id = atlas->TexRef;
|
||||
float my_tex_w = (float)atlas->TexData->Width; // Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it.
|
||||
float my_tex_h = (float)atlas->TexData->Height;
|
||||
ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h);
|
||||
|
||||
// Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it.
|
||||
float my_tex_w = (float)io.Fonts->TexData->Width;
|
||||
float my_tex_h = (float)io.Fonts->TexData->Height;
|
||||
// Basic drawing
|
||||
ImGui::SeparatorText("Image()/ImageWithBg() function");
|
||||
ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left
|
||||
ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize));
|
||||
ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
{
|
||||
ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h);
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left
|
||||
ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize));
|
||||
ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
float region_sz = 32.0f;
|
||||
float region_x = io.MousePos.x - pos.x - region_sz * 0.5f;
|
||||
float region_y = io.MousePos.y - pos.y - region_sz * 0.5f;
|
||||
float zoom = 4.0f;
|
||||
if (region_x < 0.0f) { region_x = 0.0f; }
|
||||
else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; }
|
||||
if (region_y < 0.0f) { region_y = 0.0f; }
|
||||
else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; }
|
||||
ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y);
|
||||
ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz);
|
||||
ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h);
|
||||
ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h);
|
||||
ImGui::ImageWithBg(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
// Fancy widget
|
||||
ImGui::SeparatorText("Interactive Image Viewer");
|
||||
static ExampleImageViewerData image_viewer;
|
||||
ImVec2 canvas_size(ImGui::GetContentRegionAvail().x, my_tex_h * 2.0f);
|
||||
ExampleImageViewer_DrawOptions(&image_viewer);
|
||||
ExampleImageViewer_DrawCanvas(&image_viewer, canvas_size, my_tex_id, (int)my_tex_w, (int)my_tex_h);
|
||||
|
||||
IMGUI_DEMO_MARKER("Widgets/Images/Textured buttons");
|
||||
ImGui::SeparatorText("Textured Buttons");
|
||||
ImGui::TextWrapped("And now some textured buttons..");
|
||||
static int pressed_count = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
@@ -4131,7 +4207,7 @@ static void DemoWindowWidgetsTooltips()
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::Button("Disabled item", sz);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
|
||||
ImGui::SetTooltip("I am a a tooltip for a disabled item.");
|
||||
ImGui::SetTooltip("I am a tooltip for a disabled item.");
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::TreePop();
|
||||
@@ -4148,9 +4224,9 @@ static void DemoWindowWidgetsTreeNodes()
|
||||
{
|
||||
IMGUI_DEMO_MARKER("Widgets/Tree Nodes");
|
||||
// See see "Examples -> Property Editor" (ShowExampleAppPropertyEditor() function) for a fancier, data-driven tree.
|
||||
if (ImGui::TreeNode("Basic trees"))
|
||||
if (ImGui::TreeNode("Basic Trees"))
|
||||
{
|
||||
IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic trees");
|
||||
IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic Trees");
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
// Use SetNextItemOpen() so set the default state of a node to be open. We could
|
||||
@@ -4174,9 +4250,9 @@ static void DemoWindowWidgetsTreeNodes()
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNode("Hierarchy lines"))
|
||||
if (ImGui::TreeNode("Hierarchy Lines"))
|
||||
{
|
||||
IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Hierarchy lines");
|
||||
IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Hierarchy Lines");
|
||||
static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DefaultOpen;
|
||||
HelpMarker("Default option for DrawLinesXXX is stored in style.TreeLinesFlags");
|
||||
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesNone", &base_flags, ImGuiTreeNodeFlags_DrawLinesNone);
|
||||
@@ -4213,15 +4289,57 @@ static void DemoWindowWidgetsTreeNodes()
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNode("Advanced, with Selectable nodes"))
|
||||
if (ImGui::TreeNode("Selectable Nodes"))
|
||||
{
|
||||
IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced, with Selectable nodes");
|
||||
IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Selectable Nodes");
|
||||
HelpMarker(
|
||||
"This is a more typical looking tree with selectable nodes.\n"
|
||||
"Click to select, Ctrl+Click to toggle, click on arrows or double-click to open.");
|
||||
"Manually implemented selectable nodes.\n"
|
||||
"Click to select, Ctrl+Click to toggle, click on arrows or double-click to open.\n\n"
|
||||
"You may also use the multi-select API (see 'Demo->Widgets->Selection State & Multi-Select') for more advanced multi-selection features.");
|
||||
|
||||
// Hold in 'selection_mask' a simple representation of what may be user-side selection state.
|
||||
// - You may retain selection state inside or outside your objects in whatever format you see fit.
|
||||
// You may use ImGuiSelectionBasicStorage which is conceptually close to a set<> of identifiers.
|
||||
// - We record which node was clicked and then apply selection at the end of the loop.
|
||||
// - This is a manual and simplified reimplementation of multi-selection, which the full
|
||||
// BeginMultiSelect() API implements better, but which is not trivial to wire for trees.
|
||||
static int selection_mask = 0x00;
|
||||
int node_clicked_idx = -1;
|
||||
for (int node_n = 0; node_n < 6; node_n++)
|
||||
{
|
||||
// Disable the default "open on single-click behavior" + set Selected flag according to our selection.
|
||||
// To alter selection we use if 'IsItemClicked() && !IsItemToggledOpen()', so clicking on an arrow doesn't alter selection.
|
||||
// In a BeginMultiSelect()/EndMultiSelect() we could use IsItemToggledSelection() but here we reimplement and use our own logic.
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth;
|
||||
if (selection_mask & (1 << node_n))
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
|
||||
bool is_open = ImGui::TreeNodeEx((void*)(intptr_t)node_n, flags, "Selectable Node %d", node_n);
|
||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
|
||||
node_clicked_idx = node_n;
|
||||
if (is_open)
|
||||
{
|
||||
ImGui::BulletText("<Node contents here>");
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
if (node_clicked_idx != -1)
|
||||
{
|
||||
// Update selection state (process outside of tree loop to avoid visual inconsistencies during the clicking frame)
|
||||
if (ImGui::GetIO().KeyCtrl)
|
||||
selection_mask ^= (1 << node_clicked_idx); // Ctrl+Click to toggle
|
||||
else //if (!(selection_mask & (1 << node_clicked_idx))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection
|
||||
selection_mask = (1 << node_clicked_idx); // Click to single-select
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNode("Advanced"))
|
||||
{
|
||||
IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced");
|
||||
static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth;
|
||||
static bool align_label_with_current_x_position = false;
|
||||
static bool test_drag_and_drop = false;
|
||||
static bool use_drag_and_drop = false;
|
||||
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnArrow", &base_flags, ImGuiTreeNodeFlags_OpenOnArrow);
|
||||
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick);
|
||||
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node.");
|
||||
@@ -4239,44 +4357,30 @@ static void DemoWindowWidgetsTreeNodes()
|
||||
ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesToNodes", &base_flags, ImGuiTreeNodeFlags_DrawLinesToNodes);
|
||||
|
||||
ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position);
|
||||
ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop);
|
||||
ImGui::Text("Hello!");
|
||||
ImGui::Checkbox("Make Tree Nodes as drag & drop sources", &use_drag_and_drop);
|
||||
if (align_label_with_current_x_position)
|
||||
ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
|
||||
|
||||
// 'selection_mask' is dumb representation of what may be user-side selection state.
|
||||
// You may retain selection state inside or outside your objects in whatever format you see fit.
|
||||
// 'node_clicked' is temporary storage of what node we have clicked to process selection at the end
|
||||
/// of the loop. May be a pointer to your own node type, etc.
|
||||
static int selection_mask = (1 << 2);
|
||||
int node_clicked = -1;
|
||||
for (int i = 0; i < 6; i++)
|
||||
for (int node_n = 0; node_n < 6; node_n++)
|
||||
{
|
||||
// Disable the default "open on single-click behavior" + set Selected flag according to our selection.
|
||||
// To alter selection we use IsItemClicked() && !IsItemToggledOpen(), so clicking on an arrow doesn't alter selection.
|
||||
ImGuiTreeNodeFlags node_flags = base_flags;
|
||||
const bool is_selected = (selection_mask & (1 << i)) != 0;
|
||||
if (is_selected)
|
||||
node_flags |= ImGuiTreeNodeFlags_Selected;
|
||||
if (i < 3)
|
||||
if (node_n < 3)
|
||||
{
|
||||
// Items 0..2 are Tree Node
|
||||
bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i);
|
||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
|
||||
node_clicked = i;
|
||||
if (test_drag_and_drop && ImGui::BeginDragDropSource())
|
||||
bool is_open = ImGui::TreeNodeEx((void*)(intptr_t)node_n, node_flags, "Selectable Node %d", node_n);
|
||||
if (use_drag_and_drop && ImGui::BeginDragDropSource())
|
||||
{
|
||||
ImGui::SetDragDropPayload("_TREENODE", NULL, 0);
|
||||
ImGui::SetDragDropPayload("MY_TREENODE_PAYLOAD_TYPE", NULL, 0);
|
||||
ImGui::Text("This is a drag and drop source");
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
if (i == 2 && (base_flags & ImGuiTreeNodeFlags_SpanLabelWidth))
|
||||
if (node_n == 2 && (base_flags & ImGuiTreeNodeFlags_SpanLabelWidth))
|
||||
{
|
||||
// Item 2 has an additional inline button to help demonstrate SpanLabelWidth.
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("button")) {}
|
||||
}
|
||||
if (node_open)
|
||||
if (is_open)
|
||||
{
|
||||
ImGui::BulletText("Blah blah\nBlah Blah");
|
||||
ImGui::SameLine();
|
||||
@@ -4290,26 +4394,15 @@ static void DemoWindowWidgetsTreeNodes()
|
||||
// The only reason we use TreeNode at all is to allow selection of the leaf. Otherwise we can
|
||||
// use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text().
|
||||
node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet
|
||||
ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i);
|
||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
|
||||
node_clicked = i;
|
||||
if (test_drag_and_drop && ImGui::BeginDragDropSource())
|
||||
ImGui::TreeNodeEx((void*)(intptr_t)node_n, node_flags, "Selectable Leaf %d", node_n);
|
||||
if (use_drag_and_drop && ImGui::BeginDragDropSource())
|
||||
{
|
||||
ImGui::SetDragDropPayload("_TREENODE", NULL, 0);
|
||||
ImGui::SetDragDropPayload("MY_TREENODE_PAYLOAD_TYPE", NULL, 0);
|
||||
ImGui::Text("This is a drag and drop source");
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node_clicked != -1)
|
||||
{
|
||||
// Update selection state
|
||||
// (process outside of tree loop to avoid visual inconsistencies during the clicking frame)
|
||||
if (ImGui::GetIO().KeyCtrl)
|
||||
selection_mask ^= (1 << node_clicked); // Ctrl+Click to toggle
|
||||
else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection
|
||||
selection_mask = (1 << node_clicked); // Click to single-select
|
||||
}
|
||||
if (align_label_with_current_x_position)
|
||||
ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
|
||||
ImGui::TreePop();
|
||||
@@ -8941,7 +9034,7 @@ static void ShowExampleMenuFile()
|
||||
IMGUI_DEMO_MARKER("Examples/Menu/Options");
|
||||
static bool enabled = true;
|
||||
ImGui::MenuItem("Enabled", "", &enabled);
|
||||
ImGui::BeginChild("child", ImVec2(0, 60), ImGuiChildFlags_Borders);
|
||||
ImGui::BeginChild("child", ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5.0f), ImGuiChildFlags_Borders);
|
||||
for (int i = 0; i < 10; i++)
|
||||
ImGui::Text("Scrolling Text %d", i);
|
||||
ImGui::EndChild();
|
||||
@@ -9353,6 +9446,28 @@ static void ShowExampleAppConsole(bool* p_open)
|
||||
console.Draw("Example: Console", p_open);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// [SECTION] Example App: Image Viewer / ShowExampleAppImageViewer()
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static void ShowExampleAppImageViewer(bool* p_open)
|
||||
{
|
||||
ImFontAtlas* atlas = ImGui::GetIO().Fonts;
|
||||
ImTextureRef tex_ref = atlas->TexRef; // We don't have access to other textures in this demo!
|
||||
int tex_w = atlas->TexData->Width;
|
||||
int tex_h = atlas->TexData->Height;
|
||||
if (ImGui::Begin("Example: Image Viewer", p_open))
|
||||
{
|
||||
static ExampleImageViewerData image_viewer;
|
||||
ExampleImageViewer_DrawOptions(&image_viewer);
|
||||
ImVec2 canvas_size = ImGui::GetContentRegionAvail();
|
||||
ImVec2 canvas_min_size = ImGui::IsWindowAppearing() ? ImVec2(3.0f * tex_w, 4.0f * tex_h) : ImVec2(1.0f, 1.0f);
|
||||
canvas_size = ImVec2(IM_MAX(canvas_size.x, canvas_min_size.x), IM_MAX(canvas_size.y, canvas_min_size.y));
|
||||
ExampleImageViewer_DrawCanvas(&image_viewer, canvas_size, tex_ref, tex_w, tex_h);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// [SECTION] Example App: Debug Log / ShowExampleAppLog()
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -10248,20 +10363,20 @@ static void ShowExampleAppCustomRendering(bool* p_open)
|
||||
draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon
|
||||
draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle
|
||||
draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse
|
||||
draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th); x += sz + spacing; // Square
|
||||
draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th); x += sz + spacing; // Square with all rounded corners
|
||||
draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners
|
||||
draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th); x += sz + spacing; // Square
|
||||
draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th); x += sz + spacing; // Square with all rounded corners
|
||||
draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, corners_tl_br); x += sz + spacing; // Square with two rounded corners
|
||||
draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle
|
||||
//draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle
|
||||
PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, ImDrawFlags_Closed, th); x += sz + spacing; // Concave Shape
|
||||
PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, ImDrawFlags_Closed); x += sz + spacing; // Concave Shape
|
||||
//draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th);
|
||||
draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!)
|
||||
draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!)
|
||||
draw_list->AddLineH(x, x + sz, y, col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!)
|
||||
draw_list->AddLineV(x, y, y + sz, col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!)
|
||||
draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line
|
||||
|
||||
// Path
|
||||
draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f);
|
||||
draw_list->PathStroke(col, ImDrawFlags_None, th);
|
||||
draw_list->PathStroke(col, th);
|
||||
x += sz + spacing;
|
||||
|
||||
// Quadratic Bezier Curve (3 control points)
|
||||
@@ -10395,9 +10510,9 @@ static void ShowExampleAppCustomRendering(bool* p_open)
|
||||
{
|
||||
const float GRID_STEP = 64.0f;
|
||||
for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP)
|
||||
draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40));
|
||||
draw_list->AddLineV(canvas_p0.x + x, canvas_p0.y, canvas_p1.y, IM_COL32(200, 200, 200, 40));
|
||||
for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP)
|
||||
draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40));
|
||||
draw_list->AddLineH(canvas_p0.x, canvas_p1.x, canvas_p0.y + y, IM_COL32(200, 200, 200, 40));
|
||||
}
|
||||
for (int n = 0; n < points.Size; n += 2)
|
||||
draw_list->AddLine(ImVec2(origin.x + points[n].x, origin.y + points[n].y), ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y), IM_COL32(255, 255, 0, 255), 2.0f);
|
||||
@@ -11067,10 +11182,11 @@ struct ExampleAssetsBrowser
|
||||
bool AllowBoxSelect = true; // Will set ImGuiMultiSelectFlags_BoxSelect2d
|
||||
bool AllowBoxSelectInsideSelection = false; // Will set ImGuiMultiSelectFlags_SelectOnClickAlways
|
||||
bool AllowDragUnselected = false; // Will set ImGuiMultiSelectFlags_SelectOnClickRelease
|
||||
float IconSize = 32.0f;
|
||||
float IconSize = 0;
|
||||
int IconSpacing = 10;
|
||||
int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer.
|
||||
int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer.
|
||||
bool StretchSpacing = true;
|
||||
bool UseScrollX = false; // Debug: submit twice the number of items per line (overflow horizontally to exercise ScrollX + box-select)
|
||||
|
||||
// State
|
||||
ImVector<ExampleAsset> Items; // Our items
|
||||
@@ -11121,12 +11237,15 @@ struct ExampleAssetsBrowser
|
||||
// Layout: calculate number of icon per line and number of lines
|
||||
LayoutItemSize = ImVec2(floorf(IconSize), floorf(IconSize));
|
||||
LayoutColumnCount = IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1);
|
||||
LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount;
|
||||
|
||||
// Layout: when stretching: allocate remaining space to more spacing. Round before division, so item_spacing may be non-integer.
|
||||
if (StretchSpacing && LayoutColumnCount > 1)
|
||||
LayoutItemSpacing = floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / LayoutColumnCount;
|
||||
|
||||
if (UseScrollX)
|
||||
LayoutColumnCount *= 2;
|
||||
LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount;
|
||||
|
||||
LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, LayoutItemSize.y + LayoutItemSpacing);
|
||||
LayoutSelectableSpacing = IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f);
|
||||
LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f);
|
||||
@@ -11134,6 +11253,9 @@ struct ExampleAssetsBrowser
|
||||
|
||||
void Draw(const char* title, bool* p_open)
|
||||
{
|
||||
if (IconSize <= 0.0f)
|
||||
IconSize = ImGui::CalcTextSize("99999").x;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver);
|
||||
if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar))
|
||||
{
|
||||
@@ -11183,6 +11305,7 @@ struct ExampleAssetsBrowser
|
||||
ImGui::SliderInt("Icon Spacing", &IconSpacing, 0, 32);
|
||||
ImGui::SliderInt("Icon Hit Spacing", &IconHitSpacing, 0, 32);
|
||||
ImGui::Checkbox("Stretch Spacing", &StretchSpacing);
|
||||
ImGui::Checkbox("Use ScrollX", &UseScrollX);
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
@@ -11212,7 +11335,7 @@ struct ExampleAssetsBrowser
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::SetNextWindowContentSize(ImVec2(0.0f, LayoutOuterPadding + LayoutLineCount * (LayoutItemSize.y + LayoutItemSpacing)));
|
||||
if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove))
|
||||
if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
@@ -11356,6 +11479,8 @@ struct ExampleAssetsBrowser
|
||||
}
|
||||
}
|
||||
clipper.End();
|
||||
if (Items.Size == 0)
|
||||
ImGui::Dummy(ImVec2(0, 0));
|
||||
ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing
|
||||
|
||||
// Context menu
|
||||
|
||||
+232
-164
@@ -1,4 +1,4 @@
|
||||
// dear imgui, v1.92.7
|
||||
// dear imgui, v1.92.9 WIP
|
||||
// (drawing and font code)
|
||||
|
||||
/*
|
||||
@@ -208,6 +208,7 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst)
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_CheckboxSelectedBg] = ImLerp(colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBgHovered], 0.65f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
|
||||
@@ -277,6 +278,7 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst)
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f);
|
||||
colors[ImGuiCol_CheckboxSelectedBg] = ImLerp(colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBgActive], 0.65f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f);
|
||||
@@ -347,6 +349,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst)
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_CheckboxSelectedBg] = ImVec4(0.95f, 0.97f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
|
||||
@@ -405,7 +408,6 @@ ImDrawListSharedData::ImDrawListSharedData()
|
||||
const float a = ((float)i * 2 * IM_PI) / (float)IM_COUNTOF(ArcFastVtx);
|
||||
ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a));
|
||||
}
|
||||
ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError);
|
||||
}
|
||||
|
||||
ImDrawListSharedData::~ImDrawListSharedData()
|
||||
@@ -415,17 +417,17 @@ ImDrawListSharedData::~ImDrawListSharedData()
|
||||
|
||||
void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error)
|
||||
{
|
||||
if (CircleSegmentMaxError == max_error)
|
||||
if (CircleTessellationMaxError == max_error)
|
||||
return;
|
||||
|
||||
IM_ASSERT(max_error > 0.0f);
|
||||
CircleSegmentMaxError = max_error;
|
||||
CircleTessellationMaxError = max_error;
|
||||
for (int i = 0; i < IM_COUNTOF(CircleSegmentCounts); i++)
|
||||
{
|
||||
const float radius = (float)i;
|
||||
CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : IM_DRAWLIST_ARCFAST_SAMPLE_MAX);
|
||||
CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleTessellationMaxError) : IM_DRAWLIST_ARCFAST_SAMPLE_MAX);
|
||||
}
|
||||
ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError);
|
||||
ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleTessellationMaxError);
|
||||
}
|
||||
|
||||
ImDrawList::ImDrawList(ImDrawListSharedData* shared_data)
|
||||
@@ -534,9 +536,14 @@ void ImDrawList::_PopUnusedDrawCmd()
|
||||
|
||||
void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size)
|
||||
{
|
||||
IM_ASSERT(callback != NULL);
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
if (callback == ImDrawCallback_ResetRenderState && _Data->Context != NULL && _Data->Context->PlatformIO.DrawCallback_ResetRenderState != NULL)
|
||||
callback = _Data->Context->PlatformIO.DrawCallback_ResetRenderState; // == ImGui::GetPlatformIO().DrawCallback_ResetRenderState
|
||||
#endif
|
||||
|
||||
IM_ASSERT_PARANOID(CmdBuffer.Size > 0);
|
||||
ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];
|
||||
IM_ASSERT(callback != NULL);
|
||||
IM_ASSERT(curr_cmd->UserCallback == NULL);
|
||||
if (curr_cmd->ElemCount != 0)
|
||||
{
|
||||
@@ -654,11 +661,11 @@ void ImDrawList::_OnChangedVtxOffset()
|
||||
int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const
|
||||
{
|
||||
// Automatic segment count
|
||||
const int radius_idx = (int)(radius + 0.999999f); // ceil to never reduce accuracy
|
||||
const int radius_idx = (int)(radius + 0.999f); // ceil to never reduce accuracy
|
||||
if (radius_idx >= 0 && radius_idx < IM_COUNTOF(_Data->CircleSegmentCounts))
|
||||
return _Data->CircleSegmentCounts[radius_idx]; // Use cached value
|
||||
else
|
||||
return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError);
|
||||
return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleTessellationMaxError);
|
||||
}
|
||||
|
||||
// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling)
|
||||
@@ -668,7 +675,7 @@ void ImDrawList::PushClipRect(const ImVec2& cr_min, const ImVec2& cr_max, bool i
|
||||
if (intersect_with_current_clip_rect)
|
||||
{
|
||||
ImVec4 current = _CmdHeader.ClipRect;
|
||||
if (cr.x < current.x) cr.x = current.x;
|
||||
if (cr.x < current.x) cr.x = current.x; // = ClipWith(). Note that passing inverted range wouldn't be fixed here.
|
||||
if (cr.y < current.y) cr.y = current.y;
|
||||
if (cr.z > current.z) cr.z = current.z;
|
||||
if (cr.w > current.w) cr.w = current.w;
|
||||
@@ -812,7 +819,7 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c
|
||||
|
||||
// TODO: Thickness anti-aliased lines cap are missing their AA fringe.
|
||||
// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds.
|
||||
void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags, float thickness)
|
||||
void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags)
|
||||
{
|
||||
if (points_count < 2 || (col & IM_COL32_A_MASK) == 0)
|
||||
return;
|
||||
@@ -822,6 +829,12 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32
|
||||
const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw
|
||||
const bool thick_line = (thickness > _FringeScale);
|
||||
|
||||
// If this assert triggers on legacy code:
|
||||
// - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking.
|
||||
// - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced.
|
||||
// Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8.
|
||||
IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?");
|
||||
|
||||
if (Flags & ImDrawListFlags_AntiAliasedLines)
|
||||
{
|
||||
// Anti-aliased stroke
|
||||
@@ -1438,35 +1451,13 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3,
|
||||
}
|
||||
}
|
||||
|
||||
static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags)
|
||||
{
|
||||
/*
|
||||
IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4));
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
// Obsoleted in 1.82 (from February 2021). This code was stripped/simplified and mostly commented in 1.90 (from September 2023)
|
||||
// - Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All)
|
||||
if (flags == ~0) { return ImDrawFlags_RoundCornersAll; }
|
||||
// - Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations). Read details in older version of this code.
|
||||
if (flags >= 0x01 && flags <= 0x0F) { return (flags << 4); }
|
||||
// We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'
|
||||
#endif
|
||||
*/
|
||||
// If this assert triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values.
|
||||
// Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc. anyway.
|
||||
// See details in 1.82 Changelog as well as 2021/03/12 and 2023/09/08 entries in "API BREAKING CHANGES" section.
|
||||
IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!");
|
||||
|
||||
if ((flags & ImDrawFlags_RoundCornersMask_) == 0)
|
||||
flags |= ImDrawFlags_RoundCornersAll;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags)
|
||||
{
|
||||
if (rounding >= 0.5f)
|
||||
{
|
||||
flags = FixRectCornerFlags(flags);
|
||||
if ((flags & ImDrawFlags_RoundCornersMask_) == 0)
|
||||
flags |= ImDrawFlags_RoundCornersAll;
|
||||
|
||||
rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f);
|
||||
rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f);
|
||||
}
|
||||
@@ -1494,22 +1485,48 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th
|
||||
{
|
||||
if ((col & IM_COL32_A_MASK) == 0)
|
||||
return;
|
||||
PathLineTo(p1 + ImVec2(0.5f, 0.5f));
|
||||
PathLineTo(p2 + ImVec2(0.5f, 0.5f));
|
||||
PathStroke(col, 0, thickness);
|
||||
const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) };
|
||||
AddPolyline(points, 2, col, thickness);
|
||||
}
|
||||
|
||||
void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness)
|
||||
{
|
||||
if ((col & IM_COL32_A_MASK) == 0)
|
||||
return;
|
||||
const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above.
|
||||
AddPolyline(points, 2, col, thickness);
|
||||
}
|
||||
|
||||
void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness)
|
||||
{
|
||||
if ((col & IM_COL32_A_MASK) == 0)
|
||||
return;
|
||||
const ImVec2 points[2] = { ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f) }; // Same as AddLine() above.
|
||||
AddPolyline(points, 2, col, thickness);
|
||||
}
|
||||
|
||||
// p_min = upper-left, p_max = lower-right
|
||||
// Note we don't render 1 pixels sized rectangles properly.
|
||||
void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness)
|
||||
void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags)
|
||||
{
|
||||
// If this assert triggers on legacy code:
|
||||
// - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking.
|
||||
// - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced.
|
||||
// - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundCornersXXX values.
|
||||
// If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags!
|
||||
// - Hard coded support for ~0 == ImDrawFlags_RoundCornersAll.
|
||||
// - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code.
|
||||
// - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'.
|
||||
// See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8.
|
||||
IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); // Or misuse of legacy hard-coded ImDrawCornerFlags values
|
||||
|
||||
if ((col & IM_COL32_A_MASK) == 0)
|
||||
return;
|
||||
if (Flags & ImDrawListFlags_AntiAliasedLines)
|
||||
PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags);
|
||||
else
|
||||
PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes.
|
||||
PathStroke(col, ImDrawFlags_Closed, thickness);
|
||||
PathStroke(col, thickness, ImDrawFlags_Closed);
|
||||
}
|
||||
|
||||
void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags)
|
||||
@@ -1553,7 +1570,7 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c
|
||||
PathLineTo(p2);
|
||||
PathLineTo(p3);
|
||||
PathLineTo(p4);
|
||||
PathStroke(col, ImDrawFlags_Closed, thickness);
|
||||
PathStroke(col, thickness, ImDrawFlags_Closed);
|
||||
}
|
||||
|
||||
void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col)
|
||||
@@ -1576,7 +1593,7 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p
|
||||
PathLineTo(p1);
|
||||
PathLineTo(p2);
|
||||
PathLineTo(p3);
|
||||
PathStroke(col, ImDrawFlags_Closed, thickness);
|
||||
PathStroke(col, thickness, ImDrawFlags_Closed);
|
||||
}
|
||||
|
||||
void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col)
|
||||
@@ -1611,7 +1628,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu
|
||||
PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1);
|
||||
}
|
||||
|
||||
PathStroke(col, ImDrawFlags_Closed, thickness);
|
||||
PathStroke(col, thickness, ImDrawFlags_Closed);
|
||||
}
|
||||
|
||||
void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments)
|
||||
@@ -1647,7 +1664,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_
|
||||
// Because we are filling a closed shape we remove 1 from the count of segments/points
|
||||
const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments;
|
||||
PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1);
|
||||
PathStroke(col, ImDrawFlags_Closed, thickness);
|
||||
PathStroke(col, thickness, ImDrawFlags_Closed);
|
||||
}
|
||||
|
||||
// Guaranteed to honor 'num_segments'
|
||||
@@ -1674,7 +1691,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co
|
||||
// Because we are filling a closed shape we remove 1 from the count of segments/points
|
||||
const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments;
|
||||
PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1);
|
||||
PathStroke(col, true, thickness);
|
||||
PathStroke(col, thickness, ImDrawFlags_Closed);
|
||||
}
|
||||
|
||||
void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments)
|
||||
@@ -1699,7 +1716,7 @@ void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2
|
||||
|
||||
PathLineTo(p1);
|
||||
PathBezierCubicCurveTo(p2, p3, p4, num_segments);
|
||||
PathStroke(col, 0, thickness);
|
||||
PathStroke(col, thickness);
|
||||
}
|
||||
|
||||
// Quadratic Bezier takes 3 controls points
|
||||
@@ -1710,7 +1727,7 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im
|
||||
|
||||
PathLineTo(p1);
|
||||
PathBezierQuadraticCurveTo(p2, p3, num_segments);
|
||||
PathStroke(col, 0, thickness);
|
||||
PathStroke(col, thickness);
|
||||
}
|
||||
|
||||
void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect)
|
||||
@@ -1782,7 +1799,10 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons
|
||||
if ((col & IM_COL32_A_MASK) == 0)
|
||||
return;
|
||||
|
||||
flags = FixRectCornerFlags(flags);
|
||||
IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); // If this assert triggers on legacy code: see comments in ImDrawList::PathRect().
|
||||
if ((flags & ImDrawFlags_RoundCornersMask_) == 0)
|
||||
flags |= ImDrawFlags_RoundCornersAll;
|
||||
|
||||
if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
|
||||
{
|
||||
AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col);
|
||||
@@ -2507,10 +2527,11 @@ void ImTextureData::DestroyPixels()
|
||||
// - Default texture data encoded in ASCII
|
||||
// - ImFontAtlas()
|
||||
// - ImFontAtlas::Clear()
|
||||
// - ImFontAtlas::CompactCache()
|
||||
// - ImFontAtlas::ClearFonts()
|
||||
// - ImFontAtlas::ClearInputData()
|
||||
// - ImFontAtlas::ClearTexData()
|
||||
// - ImFontAtlas::ClearFonts()
|
||||
// - ImFontAtlas::CompactCache()
|
||||
// - ImFontAtlas::SetFontLoader()
|
||||
//-----------------------------------------------------------------------------
|
||||
// - ImFontAtlasUpdateNewFrame()
|
||||
// - ImFontAtlasTextureBlockConvert()
|
||||
@@ -2547,8 +2568,6 @@ void ImTextureData::DestroyPixels()
|
||||
// - ImFontAtlasBuildPreloadAllGlyphRanges()
|
||||
// - ImFontAtlasBuildUpdatePointers()
|
||||
// - ImFontAtlasBuildRenderBitmapFromString()
|
||||
// - ImFontAtlasBuildUpdateBasicTexData()
|
||||
// - ImFontAtlasBuildUpdateLinesTexData()
|
||||
// - ImFontAtlasBuildAddFont()
|
||||
// - ImFontAtlasBuildSetupFontBakedEllipsis()
|
||||
// - ImFontAtlasBuildSetupFontBakedBlanks()
|
||||
@@ -2565,13 +2584,14 @@ void ImTextureData::DestroyPixels()
|
||||
// - ImFontAtlasUpdateDrawListsSharedData()
|
||||
//-----------------------------------------------------------------------------
|
||||
// - ImFontAtlasBuildSetTexture()
|
||||
// - ImFontAtlasBuildAddTexture()
|
||||
// - ImFontAtlasBuildMakeSpace()
|
||||
// - ImFontAtlasBuildRepackTexture()
|
||||
// - ImFontAtlasBuildGrowTexture()
|
||||
// - ImFontAtlasBuildRepackOrGrowTexture()
|
||||
// - ImFontAtlasBuildGetTextureSizeEstimate()
|
||||
// - ImFontAtlasBuildCompactTexture()
|
||||
// - ImFontAtlasBuildUpdateTexData()
|
||||
// - ImFontAtlasTextureAdd()
|
||||
// - ImFontAtlasTextureRepack()
|
||||
// - ImFontAtlasTextureGrow()
|
||||
// - ImFontAtlasTextureMakeSpace()
|
||||
// - ImFontAtlasTextureGetSizeEstimate()
|
||||
// - ImFontAtlasBuildClear()
|
||||
// - ImFontAtlasTextureCompact()
|
||||
// - ImFontAtlasBuildInit()
|
||||
// - ImFontAtlasBuildDestroy()
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -2671,9 +2691,12 @@ ImFontAtlas::~ImFontAtlas()
|
||||
TexData = NULL;
|
||||
}
|
||||
|
||||
// If you call this mid-frame, you would need to add new font and bind them!
|
||||
// You probably should not call this directly. It is not well specified.
|
||||
// If you want to replace all your fonts mid-frame, most likely you should instead call ClearFonts() then load the new fonts.
|
||||
// Calling this mid-frame will discard the CPU-side copy of the texture data which is generally unreliable as you may have textures queued for creation or updates.
|
||||
void ImFontAtlas::Clear()
|
||||
{
|
||||
IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlas::Clear()\n");
|
||||
bool backup_renderer_has_textures = RendererHasTextures;
|
||||
RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't.
|
||||
ClearFonts();
|
||||
@@ -2681,20 +2704,28 @@ void ImFontAtlas::Clear()
|
||||
RendererHasTextures = backup_renderer_has_textures;
|
||||
}
|
||||
|
||||
void ImFontAtlas::CompactCache()
|
||||
void ImFontAtlas::ClearFonts()
|
||||
{
|
||||
ImFontAtlasTextureCompact(this);
|
||||
}
|
||||
|
||||
void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader)
|
||||
{
|
||||
ImFontAtlasBuildSetupFontLoader(this, font_loader);
|
||||
// FIXME-NEWATLAS: Illegal to remove currently bound font.
|
||||
IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlas::ClearFonts()\n");
|
||||
IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!");
|
||||
for (ImFont* font : Fonts)
|
||||
ImFontAtlasBuildNotifySetFont(this, font, NULL);
|
||||
ImFontAtlasBuildDestroy(this);
|
||||
ClearInputData();
|
||||
Fonts.clear_delete();
|
||||
TexIsBuilt = false;
|
||||
for (ImDrawListSharedData* shared_data : DrawListSharedDatas)
|
||||
if (shared_data->FontAtlas == this)
|
||||
{
|
||||
shared_data->Font = NULL;
|
||||
shared_data->FontScale = shared_data->FontSize = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void ImFontAtlas::ClearInputData()
|
||||
{
|
||||
IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!");
|
||||
|
||||
for (ImFont* font : Fonts)
|
||||
ImFontAtlasFontDestroyOutput(this, font);
|
||||
for (ImFontConfig& font_cfg : Sources)
|
||||
@@ -2718,22 +2749,14 @@ void ImFontAtlas::ClearTexData()
|
||||
//Locked = true; // Hoped to be able to lock this down but some reload patterns may not be happy with it.
|
||||
}
|
||||
|
||||
void ImFontAtlas::ClearFonts()
|
||||
void ImFontAtlas::CompactCache()
|
||||
{
|
||||
// FIXME-NEWATLAS: Illegal to remove currently bound font.
|
||||
IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!");
|
||||
for (ImFont* font : Fonts)
|
||||
ImFontAtlasBuildNotifySetFont(this, font, NULL);
|
||||
ImFontAtlasBuildDestroy(this);
|
||||
ClearInputData();
|
||||
Fonts.clear_delete();
|
||||
TexIsBuilt = false;
|
||||
for (ImDrawListSharedData* shared_data : DrawListSharedDatas)
|
||||
if (shared_data->FontAtlas == this)
|
||||
{
|
||||
shared_data->Font = NULL;
|
||||
shared_data->FontScale = shared_data->FontSize = 0.0f;
|
||||
}
|
||||
ImFontAtlasTextureCompact(this);
|
||||
}
|
||||
|
||||
void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader)
|
||||
{
|
||||
ImFontAtlasBuildSetupFontLoader(this, font_loader);
|
||||
}
|
||||
|
||||
static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas)
|
||||
@@ -2760,6 +2783,28 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere
|
||||
IM_ASSERT(atlas->Builder == NULL || atlas->Builder->FrameCount < frame_count); // Protection against being called twice.
|
||||
atlas->RendererHasTextures = renderer_has_textures;
|
||||
|
||||
// Update texture status and discard old textures.
|
||||
// (we do this first thing to handle an edge case: if user mistakenly calls ClearFonts()+SetStatus(OK) during
|
||||
// rendering, it would ImFontAtlasBuildMain() rebuilding before tex->Updates[] gets a chance to be cleared)
|
||||
// (if somehow we need to move this back lower in the function, we could manually call the code to clear Updates[]).
|
||||
for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++)
|
||||
{
|
||||
// Update and remove if requested
|
||||
ImTextureData* tex = atlas->TexList[tex_n];
|
||||
if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures)
|
||||
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK.");
|
||||
|
||||
bool remove_from_list = ImTextureDataUpdateNewFrame(tex);
|
||||
if (remove_from_list)
|
||||
{
|
||||
IM_ASSERT(atlas->TexData != tex);
|
||||
tex->DestroyPixels();
|
||||
IM_DELETE(tex);
|
||||
atlas->TexList.erase(atlas->TexList.begin() + tex_n);
|
||||
tex_n--;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that font atlas was built or backend support texture reload in which case we can build now
|
||||
if (atlas->RendererHasTextures)
|
||||
{
|
||||
@@ -2799,61 +2844,49 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere
|
||||
builder->BakedPool.Size -= builder->BakedDiscardedCount;
|
||||
builder->BakedDiscardedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Update texture status
|
||||
for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++)
|
||||
bool ImTextureDataUpdateNewFrame(ImTextureData* tex)
|
||||
{
|
||||
bool remove_from_list = false;
|
||||
if (tex->Status == ImTextureStatus_OK)
|
||||
{
|
||||
ImTextureData* tex = atlas->TexList[tex_n];
|
||||
bool remove_from_list = false;
|
||||
if (tex->Status == ImTextureStatus_OK)
|
||||
{
|
||||
tex->Updates.resize(0);
|
||||
tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0;
|
||||
tex->UpdateRect.w = tex->UpdateRect.h = 0;
|
||||
}
|
||||
if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures)
|
||||
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK.");
|
||||
|
||||
// Request destroy
|
||||
// - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend.
|
||||
// - We don't destroy pixels right away, as backend may have an in-flight copy from RAM.
|
||||
if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_Destroyed && tex->Status != ImTextureStatus_WantDestroy)
|
||||
{
|
||||
IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates);
|
||||
tex->Status = ImTextureStatus_WantDestroy;
|
||||
}
|
||||
|
||||
// If a texture has never reached the backend, they don't need to know about it.
|
||||
// (note: backends between 1.92.0 and 1.92.4 could set an already destroyed texture to ImTextureStatus_WantDestroy
|
||||
// when invalidating graphics objects twice, which would previously remove it from the list and crash.)
|
||||
if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL)
|
||||
tex->Status = ImTextureStatus_Destroyed;
|
||||
|
||||
// Process texture being destroyed
|
||||
if (tex->Status == ImTextureStatus_Destroyed)
|
||||
{
|
||||
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!");
|
||||
if (tex->WantDestroyNextFrame)
|
||||
remove_from_list = true; // Destroy was scheduled by us
|
||||
else
|
||||
tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend: recreate it (e.g. freed resources mid-run)
|
||||
}
|
||||
|
||||
// The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering.
|
||||
// We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision.
|
||||
if (tex->Status == ImTextureStatus_WantDestroy)
|
||||
tex->UnusedFrames++;
|
||||
|
||||
// Destroy and remove
|
||||
if (remove_from_list)
|
||||
{
|
||||
IM_ASSERT(atlas->TexData != tex);
|
||||
tex->DestroyPixels();
|
||||
IM_DELETE(tex);
|
||||
atlas->TexList.erase(atlas->TexList.begin() + tex_n);
|
||||
tex_n--;
|
||||
}
|
||||
tex->Updates.resize(0);
|
||||
tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0;
|
||||
tex->UpdateRect.w = tex->UpdateRect.h = 0;
|
||||
}
|
||||
|
||||
// Request destroy
|
||||
// - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend.
|
||||
// - We don't destroy pixels right away, as backend may have an in-flight copy from RAM.
|
||||
if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_Destroyed && tex->Status != ImTextureStatus_WantDestroy)
|
||||
{
|
||||
IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates);
|
||||
tex->Status = ImTextureStatus_WantDestroy;
|
||||
}
|
||||
|
||||
// If a texture has never reached the backend, they don't need to know about it.
|
||||
// (note: backends between 1.92.0 and 1.92.4 could set an already destroyed texture to ImTextureStatus_WantDestroy
|
||||
// when invalidating graphics objects twice, which would previously remove it from the list and crash.)
|
||||
if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL)
|
||||
tex->Status = ImTextureStatus_Destroyed;
|
||||
|
||||
// Process texture being destroyed
|
||||
if (tex->Status == ImTextureStatus_Destroyed)
|
||||
{
|
||||
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!");
|
||||
if (tex->WantDestroyNextFrame)
|
||||
remove_from_list = true; // Destroy was scheduled by us
|
||||
else
|
||||
tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend: recreate it (e.g. freed resources mid-run)
|
||||
}
|
||||
|
||||
// The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering.
|
||||
// We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision.
|
||||
if (tex->Status == ImTextureStatus_WantDestroy)
|
||||
tex->UnusedFrames++;
|
||||
|
||||
return remove_from_list;
|
||||
}
|
||||
|
||||
void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h)
|
||||
@@ -2967,12 +3000,17 @@ void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, I
|
||||
memcpy(dst_tex->GetPixelsAt(dst_x, dst_y + y), src_tex->GetPixelsAt(src_x, src_y + y), w * dst_tex->BytesPerPixel);
|
||||
}
|
||||
|
||||
// Queue texture block update for renderer backend
|
||||
void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h)
|
||||
{
|
||||
ImTextureDataQueueUpload(tex, x, y, w, h);
|
||||
atlas->TexIsBuilt = false;
|
||||
}
|
||||
|
||||
// Queue texture block update for renderer backend
|
||||
void ImTextureDataQueueUpload(ImTextureData* tex, int x, int y, int w, int h)
|
||||
{
|
||||
IM_ASSERT(tex->Status != ImTextureStatus_WantDestroy && tex->Status != ImTextureStatus_Destroyed);
|
||||
IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000);
|
||||
IM_UNUSED(atlas);
|
||||
|
||||
ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h };
|
||||
int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w);
|
||||
@@ -2985,7 +3023,6 @@ void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex,
|
||||
tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y);
|
||||
tex->UsedRect.w = (unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w, req.x + req.w) - tex->UsedRect.x);
|
||||
tex->UsedRect.h = (unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h, req.y + req.h) - tex->UsedRect.y);
|
||||
atlas->TexIsBuilt = false;
|
||||
|
||||
// No need to queue if status is == ImTextureStatus_WantCreate
|
||||
if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates)
|
||||
@@ -3056,7 +3093,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in)
|
||||
}
|
||||
else
|
||||
{
|
||||
IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before.
|
||||
IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font!"); // When using MergeMode make sure that a font has already been added before.
|
||||
font = font_cfg_in->DstFont ? font_cfg_in->DstFont : Fonts.back();
|
||||
ImFontAtlasFontDiscardBakes(this, font, 0); // Need to discard bakes if the font was already used, because baked->FontLoaderDatas[] will change size. (#9162)
|
||||
}
|
||||
@@ -3084,6 +3121,11 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in)
|
||||
IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL);
|
||||
IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet.
|
||||
}
|
||||
// | Target w/ Implicit RefSize | Target w/ Explicit RefSize |
|
||||
// Adding w/ Implicit RefSize: | OK (same scale) | OK (same scale) |
|
||||
// Adding w/ Explicit RefSize: | KO | OK (custom scale) |
|
||||
if (font_cfg_in->MergeMode && font_cfg_in->SizePixels > 0)
|
||||
IM_ASSERT((font->Flags & ImFontFlags_ImplicitRefSize) == 0 && "Cannot use MergeMode with an explicit reference size when the destination font used an implicit reference size!");
|
||||
IM_ASSERT(font_cfg->FontLoaderData == NULL);
|
||||
|
||||
if (!ImFontAtlasFontSourceInit(this, font_cfg))
|
||||
@@ -3120,8 +3162,10 @@ static void Decode85(const unsigned char* src, unsigned char* dst)
|
||||
dst += 4;
|
||||
}
|
||||
}
|
||||
#ifndef IMGUI_DISABLE_DEFAULT_FONT
|
||||
#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP)
|
||||
static const char* GetDefaultCompressedFontDataProggyClean(int* out_size);
|
||||
#endif
|
||||
#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR)
|
||||
static const char* GetDefaultCompressedFontDataProggyForever(int* out_size);
|
||||
#endif
|
||||
|
||||
@@ -3146,12 +3190,15 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg)
|
||||
// If you want a similar font which may be better scaled, consider using AddFontDefaultVector().
|
||||
ImFont* ImFontAtlas::AddFontDefaultBitmap(const ImFontConfig* font_cfg_template)
|
||||
{
|
||||
#ifndef IMGUI_DISABLE_DEFAULT_FONT
|
||||
#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP)
|
||||
ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
|
||||
if (!font_cfg_template)
|
||||
font_cfg.PixelSnapH = true; // Prevents sub-integer scaling factors at lower-level layers.
|
||||
if (font_cfg.SizePixels <= 0.0f)
|
||||
{
|
||||
font_cfg.SizePixels = 13.0f; // This only serves (1) as a reference for GlyphOffset.y setting and (2) as a default for pre-1.92 backend.
|
||||
font_cfg.Flags |= ImFontFlags_ImplicitRefSize;
|
||||
}
|
||||
if (font_cfg.Name[0] == '\0')
|
||||
ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "ProggyClean.ttf");
|
||||
font_cfg.EllipsisChar = (ImWchar)0x0085;
|
||||
@@ -3164,19 +3211,22 @@ ImFont* ImFontAtlas::AddFontDefaultBitmap(const ImFontConfig* font_cfg_template)
|
||||
IM_ASSERT(0 && "Function is disabled in this build.");
|
||||
IM_UNUSED(font_cfg_template);
|
||||
return NULL;
|
||||
#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT
|
||||
#endif
|
||||
}
|
||||
|
||||
// Load a minimal version of ProggyForever, designed to match our good old ProggyClean, but nicely scalable.
|
||||
// (See build script in https://github.com/ocornut/proggyforever for details)
|
||||
ImFont* ImFontAtlas::AddFontDefaultVector(const ImFontConfig* font_cfg_template)
|
||||
{
|
||||
#ifndef IMGUI_DISABLE_DEFAULT_FONT
|
||||
#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR)
|
||||
ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
|
||||
if (!font_cfg_template)
|
||||
font_cfg.PixelSnapH = true; // Precisely match ProggyClean, but prevents sub-integer scaling factors at lower-level layers.
|
||||
if (font_cfg.SizePixels <= 0.0f)
|
||||
{
|
||||
font_cfg.SizePixels = 13.0f;
|
||||
font_cfg.Flags |= ImFontFlags_ImplicitRefSize;
|
||||
}
|
||||
if (font_cfg.Name[0] == '\0')
|
||||
ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "ProggyForever.ttf");
|
||||
font_cfg.ExtraSizeScale *= 1.015f; // Match ProggyClean
|
||||
@@ -3189,7 +3239,7 @@ ImFont* ImFontAtlas::AddFontDefaultVector(const ImFontConfig* font_cfg_template)
|
||||
IM_ASSERT(0 && "Function is disabled in this build.");
|
||||
IM_UNUSED(font_cfg_template);
|
||||
return NULL;
|
||||
#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT
|
||||
#endif
|
||||
}
|
||||
|
||||
ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)
|
||||
@@ -3545,7 +3595,7 @@ void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, in
|
||||
}
|
||||
}
|
||||
|
||||
static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas)
|
||||
static void ImFontAtlasBuildUpdateTexDataBasic(ImFontAtlas* atlas)
|
||||
{
|
||||
// Pack and store identifier so we can refresh UV coordinates on texture resize.
|
||||
// FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing.
|
||||
@@ -3579,7 +3629,7 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas)
|
||||
atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y);
|
||||
}
|
||||
|
||||
static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas)
|
||||
static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas)
|
||||
{
|
||||
if (atlas->Flags & ImFontAtlasFlags_NoBakedLines)
|
||||
return;
|
||||
@@ -4028,6 +4078,12 @@ static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex)
|
||||
ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, atlas->TexRef);
|
||||
}
|
||||
|
||||
static void ImFontAtlasBuildUpdateTexData(ImFontAtlas* atlas)
|
||||
{
|
||||
ImFontAtlasBuildUpdateTexDataBasic(atlas);
|
||||
ImFontAtlasBuildUpdateTexDataLines(atlas);
|
||||
}
|
||||
|
||||
// Create a new texture, discard previous one
|
||||
ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h)
|
||||
{
|
||||
@@ -4142,8 +4198,7 @@ void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h)
|
||||
}
|
||||
|
||||
// Update other cached UV
|
||||
ImFontAtlasBuildUpdateLinesTexData(atlas);
|
||||
ImFontAtlasBuildUpdateBasicTexData(atlas);
|
||||
ImFontAtlasBuildUpdateTexData(atlas);
|
||||
|
||||
builder->LockDisableResize = false;
|
||||
ImFontAtlasUpdateDrawListsSharedData(atlas);
|
||||
@@ -4292,8 +4347,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas)
|
||||
ImFontAtlasPackInit(atlas);
|
||||
|
||||
// Add required texture data
|
||||
ImFontAtlasBuildUpdateLinesTexData(atlas);
|
||||
ImFontAtlasBuildUpdateBasicTexData(atlas);
|
||||
ImFontAtlasBuildUpdateTexData(atlas);
|
||||
|
||||
// Register fonts
|
||||
ImFontAtlasBuildUpdatePointers(atlas);
|
||||
@@ -4322,6 +4376,8 @@ void ImFontAtlasBuildDestroy(ImFontAtlas* atlas)
|
||||
atlas->Builder = NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void ImFontAtlasPackInit(ImFontAtlas * atlas)
|
||||
{
|
||||
ImTextureData* tex = atlas->TexData;
|
||||
@@ -5718,8 +5774,13 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im
|
||||
if (glyph->Colored)
|
||||
col |= ~IM_COL32_A_MASK;
|
||||
float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f;
|
||||
float x = IM_TRUNC(pos.x);
|
||||
float y = IM_TRUNC(pos.y);
|
||||
float x = pos.x;
|
||||
float y = pos.y;
|
||||
if ((draw_list->Flags & ImDrawListFlags_TextNoPixelSnap) == 0)
|
||||
{
|
||||
x = IM_TRUNC(x);
|
||||
y = IM_TRUNC(y);
|
||||
}
|
||||
|
||||
float x1 = x + glyph->X0 * scale;
|
||||
float x2 = x + glyph->X1 * scale;
|
||||
@@ -5751,12 +5812,17 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im
|
||||
// DO NOT CALL DIRECTLY THIS WILL CHANGE WILDLY IN 2026. Use ImDrawList::AddText().
|
||||
void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, ImDrawTextFlags flags)
|
||||
{
|
||||
// Align to be pixel perfect
|
||||
begin:
|
||||
float x = IM_TRUNC(pos.x);
|
||||
float y = IM_TRUNC(pos.y);
|
||||
// Align to be pixel perfect
|
||||
float x = pos.x;
|
||||
float y = pos.y;
|
||||
if (y > clip_rect.w)
|
||||
return;
|
||||
if ((draw_list->Flags & ImDrawListFlags_TextNoPixelSnap) == 0)
|
||||
{
|
||||
x = IM_TRUNC(x);
|
||||
y = IM_TRUNC(y);
|
||||
}
|
||||
|
||||
if (!text_end)
|
||||
text_end = text_begin + ImStrlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls.
|
||||
@@ -6021,7 +6087,7 @@ void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float
|
||||
draw_list->PathLineTo(ImVec2(bx - third, by - third));
|
||||
draw_list->PathLineTo(ImVec2(bx, by));
|
||||
draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f));
|
||||
draw_list->PathStroke(col, 0, thickness);
|
||||
draw_list->PathStroke(col, thickness);
|
||||
}
|
||||
|
||||
// Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side.
|
||||
@@ -6151,8 +6217,8 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p
|
||||
flags = ImDrawFlags_RoundCornersDefault_;
|
||||
if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
|
||||
{
|
||||
ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col));
|
||||
ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col));
|
||||
ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col));
|
||||
ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col));
|
||||
draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags);
|
||||
|
||||
int yi = 0;
|
||||
@@ -6161,12 +6227,12 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p
|
||||
float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
|
||||
if (y2 <= y1)
|
||||
continue;
|
||||
for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
|
||||
for (float x = p_min.x + grid_off.x + ((yi ^ 1) & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
|
||||
{
|
||||
float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
|
||||
if (x2 <= x1)
|
||||
continue;
|
||||
ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone;
|
||||
ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; // FIXME: Could use CalcRoundingFlagsForRectInRect()
|
||||
if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; }
|
||||
if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; }
|
||||
|
||||
@@ -6308,7 +6374,7 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i
|
||||
// Download and more information at https://github.com/bluescan/proggyfonts
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef IMGUI_DISABLE_DEFAULT_FONT
|
||||
#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP)
|
||||
|
||||
// File: 'ProggyClean.ttf' (41208 bytes)
|
||||
// Exported using binary_to_compressed_c.exe -u8 "ProggyClean.ttf" proggy_clean_ttf
|
||||
@@ -6488,6 +6554,7 @@ static const char* GetDefaultCompressedFontDataProggyClean(int* out_size)
|
||||
*out_size = proggy_clean_ttf_compressed_size;
|
||||
return (const char*)proggy_clean_ttf_compressed_data;
|
||||
}
|
||||
#endif // #if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// [SECTION] Default font data (ProggyForever-Regular-minimal.ttf)
|
||||
@@ -6496,6 +6563,8 @@ static const char* GetDefaultCompressedFontDataProggyClean(int* out_size)
|
||||
// MIT license / Copyright (c) 2026 Disco Hello, Copyright (c) 2019,2023 Tristan Grimmer
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR)
|
||||
|
||||
// File: 'output/ProggyForever-Regular-minimal.ttf' (18556 bytes)
|
||||
// Exported using binary_to_compressed_c.exe -u8 "output/ProggyForever-Regular-minimal.ttf" proggy_forever_minimal_ttf
|
||||
static const unsigned int proggy_forever_minimal_ttf_compressed_size = 14562;
|
||||
@@ -6750,7 +6819,6 @@ static const char* GetDefaultCompressedFontDataProggyForever(int* out_size)
|
||||
*out_size = proggy_forever_minimal_ttf_compressed_size;
|
||||
return (const char*)proggy_forever_minimal_ttf_compressed_data;
|
||||
}
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT
|
||||
#endif // #if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR)
|
||||
|
||||
#endif // #ifndef IMGUI_DISABLE
|
||||
|
||||
+9
-1
@@ -878,7 +878,15 @@ inline void ImGui::FileBrowser::UpdateFileRecords()
|
||||
|
||||
inline void ImGui::FileBrowser::SetPwdUncatched(const std::filesystem::path &pwd)
|
||||
{
|
||||
pwd_ = absolute(pwd);
|
||||
// spice2x
|
||||
// avoid MinGW std::filesystem::absolute() duplicating UNC server/share
|
||||
// prefixes, which makes directory_iterator() throw during startup
|
||||
// spice2x
|
||||
const auto &nativePwd = pwd.native();
|
||||
const bool hasUncPrefix = nativePwd.size() >= 2 &&
|
||||
(nativePwd[0] == '\\' || nativePwd[0] == '/') &&
|
||||
(nativePwd[1] == '\\' || nativePwd[1] == '/');
|
||||
pwd_ = hasUncPrefix ? pwd : absolute(pwd);
|
||||
UpdateFileRecords();
|
||||
selectedFilenames_.clear();
|
||||
(*inputNameBuf_)[0] = '\0';
|
||||
|
||||
+28
-9
@@ -1,4 +1,4 @@
|
||||
// dear imgui, v1.92.7
|
||||
// dear imgui, v1.92.9 WIP
|
||||
// (internal structures/api)
|
||||
|
||||
// You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility.
|
||||
@@ -263,6 +263,9 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer
|
||||
#define IMGUI_DEBUG_LOG_DOCKING(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventDocking) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)
|
||||
#define IMGUI_DEBUG_LOG_VIEWPORT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventViewport) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)
|
||||
|
||||
// Debug options (also see ones on top of imgui.cpp)
|
||||
//#define IMGUI_DEBUG_BOXSELECT
|
||||
|
||||
// Static Asserts
|
||||
#define IM_STATIC_ASSERT(_COND) static_assert(_COND, "")
|
||||
|
||||
@@ -517,7 +520,8 @@ inline double ImRsqrt(double x) { return 1.0 / sqrt(x); }
|
||||
template<typename T> T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; }
|
||||
template<typename T> T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; }
|
||||
template<typename T> T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; }
|
||||
template<typename T> T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); }
|
||||
template<typename T> T ImLerp(double a, double b, float t) { return (T)(a + (b - a) * (double)t); }
|
||||
template<typename T> T ImLerp(T a, T b, float t) { return (T)((float)a + (float)(b - a) * t); }
|
||||
template<typename T> void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; }
|
||||
template<typename T> T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; }
|
||||
template<typename T> T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; }
|
||||
@@ -538,6 +542,7 @@ inline float ImFloor(float f) { return
|
||||
inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); }
|
||||
inline float ImTrunc64(float f) { return (float)(ImS64)(f); }
|
||||
inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } // FIXME: Positive values only.
|
||||
inline float ImCeilFast(float f) { int i = (int)f; return (float)(i + (f > (float)i)); } // Consider using the the bit-hack version (search for "0x1p120f").
|
||||
inline int ImModPositive(int a, int b) { return (a + b) % b; }
|
||||
inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; }
|
||||
inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); }
|
||||
@@ -614,6 +619,8 @@ struct IMGUI_API ImRect
|
||||
bool Overlaps(const ImRect& r) const { return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; }
|
||||
void Add(const ImVec2& p) { if (Min.x > p.x) Min.x = p.x; if (Min.y > p.y) Min.y = p.y; if (Max.x < p.x) Max.x = p.x; if (Max.y < p.y) Max.y = p.y; }
|
||||
void Add(const ImRect& r) { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; }
|
||||
void AddX(float x) { if (Min.x > x) Min.x = x; if (Max.x < x) Max.x = x; }
|
||||
void AddY(float y) { if (Min.y > y) Min.y = y; if (Max.y < y) Max.y = y; }
|
||||
void Expand(const float amount) { Min.x -= amount; Min.y -= amount; Max.x += amount; Max.y += amount; }
|
||||
void Expand(const ImVec2& amount) { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; }
|
||||
void Translate(const ImVec2& d) { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; }
|
||||
@@ -877,7 +884,7 @@ struct IMGUI_API ImDrawListSharedData
|
||||
float FontSize; // Current font size (used for for simplified AddText overload)
|
||||
float FontScale; // Current font scale (== FontSize / Font->FontSize)
|
||||
float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo()
|
||||
float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc
|
||||
float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc
|
||||
float InitialFringeScale; // Initial scale to apply to AA fringe
|
||||
ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards)
|
||||
ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen()
|
||||
@@ -1010,6 +1017,7 @@ enum ImGuiItemStatusFlags_
|
||||
ImGuiItemStatusFlags_HasClipRect = 1 << 9, // g.LastItemData.ClipRect is valid.
|
||||
ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd().
|
||||
//ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb.
|
||||
ImGuiItemStatusFlags_EditedInternal = 1 << 11, // Similar to ImGuiItemStatusFlags_Edited but bypassing ImGuiItemFlags_NoMarkEdited.
|
||||
|
||||
// Additional status + semantic for ImGuiTestEngine
|
||||
#ifdef IMGUI_ENABLE_TEST_ENGINE
|
||||
@@ -1215,6 +1223,7 @@ struct IMGUI_API ImGuiMenuColumns
|
||||
};
|
||||
|
||||
// Internal temporary state for deactivating InputText() instances.
|
||||
// Store as part of ImGuiDeactivatedItemData?
|
||||
struct IMGUI_API ImGuiInputTextDeactivatedState
|
||||
{
|
||||
ImGuiID ID; // widget id owning the text state (which just got deactivated)
|
||||
@@ -1468,6 +1477,7 @@ struct ImGuiPtrOrIndex
|
||||
};
|
||||
|
||||
// Data used by IsItemDeactivated()/IsItemDeactivatedAfterEdit() functions
|
||||
// Also see ImGuiInputTextDeactivatedState which is an extension for this for InputText()
|
||||
struct ImGuiDeactivatedItemData
|
||||
{
|
||||
ImGuiID ID;
|
||||
@@ -1715,6 +1725,7 @@ enum ImGuiActivateFlags_
|
||||
};
|
||||
|
||||
// Early work-in-progress API for ScrollToItem()
|
||||
// FIXME: Missing flags to request making both edges visible when possible.
|
||||
enum ImGuiScrollFlags_
|
||||
{
|
||||
ImGuiScrollFlags_None = 0,
|
||||
@@ -1912,6 +1923,7 @@ struct ImGuiBoxSelectState
|
||||
// Temporary/Transient data
|
||||
bool UnclipMode; // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select.
|
||||
ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets.
|
||||
ImRect UnclipRects[2]; // Per-axis versions.
|
||||
ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos)
|
||||
ImRect BoxSelectRectCurr;
|
||||
|
||||
@@ -1934,7 +1946,8 @@ struct IMGUI_API ImGuiMultiSelectTempData
|
||||
ImGuiMultiSelectFlags Flags;
|
||||
ImVec2 ScopeRectMin;
|
||||
ImVec2 BackupCursorMaxPos;
|
||||
ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges.
|
||||
//ImGuiSelectionUserData CurrSubmittedItem; // Copy of last submitted item data, used to merge output ranges.
|
||||
//ImGuiSelectionUserData PrevSubmittedItem; // Copy of previous submitted item data, used to merge output ranges.
|
||||
ImGuiID BoxSelectId;
|
||||
ImGuiKeyChord KeyMods;
|
||||
ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all.
|
||||
@@ -2022,7 +2035,7 @@ enum ImGuiDockNodeState
|
||||
ImGuiDockNodeState_HostWindowVisible,
|
||||
};
|
||||
|
||||
// sizeof() 156~192
|
||||
// sizeof() 176~216
|
||||
struct IMGUI_API ImGuiDockNode
|
||||
{
|
||||
ImGuiID ID;
|
||||
@@ -2039,8 +2052,8 @@ struct IMGUI_API ImGuiDockNode
|
||||
ImVec2 Size; // Current size
|
||||
ImVec2 SizeRef; // [Split node only] Last explicitly written-to size (overridden when using a splitter affecting the node), used to calculate Size.
|
||||
ImGuiAxis SplitAxis; // [Split node only] Split axis (X or Y)
|
||||
ImGuiWindowClass WindowClass; // [Root node only]
|
||||
ImU32 LastBgColor;
|
||||
ImGuiWindowClass WindowClass; // [Root node only]
|
||||
|
||||
ImGuiWindow* HostWindow;
|
||||
ImGuiWindow* VisibleWindow; // Generally point to window which is ID is == SelectedTabID, but when CTRL+Tabbing this can be a different window.
|
||||
@@ -2410,6 +2423,7 @@ struct ImGuiContext
|
||||
float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale
|
||||
ImDrawListSharedData DrawListSharedData;
|
||||
ImGuiID WithinEndChildID; // Set within EndChild()
|
||||
ImGuiID WithinEndPopupID; // Set within EndPopup()
|
||||
void* TestEngine; // Test engine user data
|
||||
|
||||
// Inputs
|
||||
@@ -3439,7 +3453,7 @@ namespace ImGui
|
||||
IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags);
|
||||
|
||||
// Fonts, drawing
|
||||
IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL: DO NOT USE YET.
|
||||
IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL.
|
||||
IMGUI_API void UnregisterUserTexture(ImTextureData* tex);
|
||||
IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas);
|
||||
IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas);
|
||||
@@ -3557,6 +3571,7 @@ namespace ImGui
|
||||
|
||||
// Childs
|
||||
IMGUI_API bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags);
|
||||
IMGUI_API ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window);
|
||||
|
||||
// Popups, Modals
|
||||
IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags);
|
||||
@@ -3662,7 +3677,7 @@ namespace ImGui
|
||||
IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key);
|
||||
IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0);
|
||||
IMGUI_API void SetKeyOwnersForKeyChord(ImGuiKeyChord key, ImGuiID owner_id, ImGuiInputFlags flags = 0);
|
||||
IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'.
|
||||
IMGUI_API bool SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags);
|
||||
IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id'
|
||||
inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiContext* ctx, ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &ctx->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; }
|
||||
|
||||
@@ -3765,6 +3780,7 @@ namespace ImGui
|
||||
// We don't use the ID Stack for this as it is common to want them separate.
|
||||
IMGUI_API void PushFocusScope(ImGuiID id);
|
||||
IMGUI_API void PopFocusScope();
|
||||
IMGUI_API bool IsInNavFocusRoute(ImGuiID focus_scope_id);
|
||||
inline ImGuiID GetCurrentFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentFocusScopeId; } // Focus scope we are outputting into, set by PushFocusScope()
|
||||
|
||||
// Drag and Drop
|
||||
@@ -3774,7 +3790,7 @@ namespace ImGui
|
||||
IMGUI_API void ClearDragDrop();
|
||||
IMGUI_API bool IsDragDropPayloadBeingAccepted();
|
||||
IMGUI_API void RenderDragDropTargetRectForItem(const ImRect& bb);
|
||||
IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb);
|
||||
IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding);
|
||||
|
||||
// Typing-Select API
|
||||
// (provide Windows Explorer style "select items by typing partial name" + "cycle through items by typing same letter" feature)
|
||||
@@ -3831,6 +3847,7 @@ namespace ImGui
|
||||
IMGUI_API void TableUpdateLayout(ImGuiTable* table);
|
||||
IMGUI_API void TableUpdateBorders(ImGuiTable* table);
|
||||
IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table);
|
||||
IMGUI_API void TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect);
|
||||
IMGUI_API void TableDrawBorders(ImGuiTable* table);
|
||||
IMGUI_API void TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags_for_section_to_display);
|
||||
IMGUI_API bool TableBeginContextMenuPopup(ImGuiTable* table);
|
||||
@@ -4261,6 +4278,8 @@ IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex,
|
||||
IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h);
|
||||
IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h);
|
||||
|
||||
IMGUI_API bool ImTextureDataUpdateNewFrame(ImTextureData* tex);
|
||||
IMGUI_API void ImTextureDataQueueUpload(ImTextureData* tex, int x, int y, int w, int h);
|
||||
IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format);
|
||||
IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status);
|
||||
IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format);
|
||||
|
||||
+835
-835
File diff suppressed because it is too large
Load Diff
+50
-27
@@ -1,4 +1,4 @@
|
||||
// dear imgui, v1.92.7
|
||||
// dear imgui, v1.92.9 WIP
|
||||
// (tables and columns code)
|
||||
|
||||
/*
|
||||
@@ -240,6 +240,7 @@ Index of this file:
|
||||
#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'
|
||||
#pragma GCC diagnostic ignored "-Wstrict-overflow"
|
||||
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
|
||||
#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may change value
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result
|
||||
#endif
|
||||
|
||||
@@ -1315,14 +1316,32 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
|
||||
table->InnerWindow->DecoInnerSizeY1 = table_instance->LastFrozenHeight;
|
||||
table_instance->LastFrozenHeight = 0.0f;
|
||||
|
||||
// Initial state
|
||||
ImGuiWindow* inner_window = table->InnerWindow;
|
||||
ImGuiBoxSelectState* bs = &g.BoxSelectState;
|
||||
if (bs->Window == inner_window && bs->UnclipMode)
|
||||
TableApplyExternalUnclipRect(table, bs->UnclipRect);
|
||||
|
||||
// Initial state
|
||||
if (table->Flags & ImGuiTableFlags_NoClip)
|
||||
table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP);
|
||||
else
|
||||
inner_window->DrawList->PushClipRect(inner_window->InnerClipRect.Min, inner_window->InnerClipRect.Max, false); // FIXME: use table->InnerClipRect?
|
||||
}
|
||||
|
||||
// When starting a BeginMultiSelect() after table has been layout we update IsRequestOutput fields.
|
||||
void ImGui::TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect)
|
||||
{
|
||||
if (rect.IsInverted())
|
||||
return;
|
||||
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
|
||||
{
|
||||
ImGuiTableColumn* column = &table->Columns[column_n];
|
||||
if (!column->IsRequestOutput)
|
||||
if (rect.Overlaps(ImRect(column->MinX, table->WorkRect.Min.y, column->MaxX, FLT_MAX)))
|
||||
column->IsRequestOutput = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Process hit-testing on resizing borders. Actual size change will be applied in EndTable()
|
||||
// - Set table->HoveredColumnBorder with a short delay/timer to reduce visual feedback noise.
|
||||
void ImGui::TableUpdateBorders(ImGuiTable* table)
|
||||
@@ -1436,12 +1455,12 @@ void ImGui::EndTable()
|
||||
if (table->Flags & ImGuiTableFlags_ScrollX)
|
||||
{
|
||||
const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;
|
||||
float max_pos_x = table->InnerWindow->DC.CursorMaxPos.x;
|
||||
float max_pos_x = inner_window->DC.CursorMaxPos.x;
|
||||
if (table->RightMostEnabledColumn != -1)
|
||||
max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border);
|
||||
if (table->ResizedColumn != -1)
|
||||
max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2);
|
||||
table->InnerWindow->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth;
|
||||
inner_window->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth;
|
||||
}
|
||||
|
||||
// Pop clipping rect
|
||||
@@ -1550,7 +1569,7 @@ void ImGui::EndTable()
|
||||
}
|
||||
else
|
||||
{
|
||||
table->InnerWindow->DC.TreeDepth--;
|
||||
inner_window->DC.TreeDepth--;
|
||||
ItemSize(table->OuterRect.GetSize());
|
||||
ItemAdd(table->OuterRect, 0);
|
||||
}
|
||||
@@ -1565,13 +1584,12 @@ void ImGui::EndTable()
|
||||
}
|
||||
else if (temp_data->UserOuterSize.x <= 0.0f)
|
||||
{
|
||||
// Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block
|
||||
// - Checking for ImGuiTableFlags_ScrollX/ScrollY flag makes us a frame ahead when disabling those flags.
|
||||
// - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback.
|
||||
const float inner_content_max_x = table->OuterRect.Min.x + table->ColumnsAutoFitWidth; // Slightly misleading name but used for code symmetry with inner_content_max_y
|
||||
const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.x : 0.0f);
|
||||
outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, inner_content_max_x + decoration_size - temp_data->UserOuterSize.x);
|
||||
outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, inner_content_max_x + decoration_size));
|
||||
// Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block, #9352
|
||||
// - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback? See broken test in 'table_reported_size_outer'
|
||||
const float outer_content_max_x = table->OuterRect.Min.x + table->ColumnsAutoFitWidth;
|
||||
const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((inner_window != outer_window) ? inner_window->ScrollbarSizes.x : 0.0f);
|
||||
outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, outer_content_max_x + decoration_size - temp_data->UserOuterSize.x);
|
||||
outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, outer_content_max_x + decoration_size));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1579,9 +1597,12 @@ void ImGui::EndTable()
|
||||
}
|
||||
if (temp_data->UserOuterSize.y <= 0.0f)
|
||||
{
|
||||
const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.y : 0.0f;
|
||||
outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y);
|
||||
outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y + decoration_size));
|
||||
// (same comment as above)
|
||||
const float outer_content_size_y = (inner_window == outer_window) ? (inner_content_max_y - table->InnerRect.Min.y) : (inner_content_max_y - inner_window->DC.CursorStartPos.y);
|
||||
const float outer_content_max_y = table->OuterRect.Min.y + outer_content_size_y;
|
||||
const float decoration_size = (inner_window != outer_window ? inner_window->ScrollbarSizes.y : 0.0f);
|
||||
outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, outer_content_max_y + decoration_size - temp_data->UserOuterSize.y);
|
||||
outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, outer_content_max_y + decoration_size));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1641,7 +1662,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo
|
||||
ImGuiTable* table = g.CurrentTable;
|
||||
IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!");
|
||||
IM_ASSERT_USER_ERROR_RET(table->DeclColumnsCount < table->ColumnsCount, "TableSetupColumn(): called too many times!");
|
||||
IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!");
|
||||
IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!"); // Table layout is locked when submitting a row or when calling BeginMultiSelect() with box-select.
|
||||
IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()");
|
||||
|
||||
ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount];
|
||||
@@ -2053,11 +2074,11 @@ void ImGui::TableEndRow(ImGuiTable* table)
|
||||
|
||||
// Draw top border
|
||||
if (top_border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y)
|
||||
window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), top_border_col, border_size);
|
||||
window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y1, top_border_col, border_size);
|
||||
|
||||
// Draw bottom border at the row unfreezing mark (always strong)
|
||||
if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y)
|
||||
window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size);
|
||||
window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y2, table->BorderColorStrong, border_size);
|
||||
}
|
||||
|
||||
// End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle)
|
||||
@@ -2834,7 +2855,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table)
|
||||
else if ((table->Flags & (ImGuiTableFlags_NoBordersInBodyUntilResize | ImGuiTableFlags_NoBordersInBody)) == 0)
|
||||
draw_y2 = draw_y2_body;
|
||||
if (draw_y2 > draw_y1)
|
||||
inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), TableGetColumnBorderCol(table, order_n, column_n), border_size);
|
||||
inner_drawlist->AddLineV(column->MaxX, draw_y1, draw_y2, TableGetColumnBorderCol(table, order_n, column_n), border_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2851,17 +2872,17 @@ void ImGui::TableDrawBorders(ImGuiTable* table)
|
||||
const ImU32 outer_col = table->BorderColorStrong;
|
||||
if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter)
|
||||
{
|
||||
inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size);
|
||||
inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, border_size);
|
||||
}
|
||||
else if (table->Flags & ImGuiTableFlags_BordersOuterV)
|
||||
{
|
||||
inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size);
|
||||
inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size);
|
||||
inner_drawlist->AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size);
|
||||
inner_drawlist->AddLineV(outer_border.Max.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size);
|
||||
}
|
||||
else if (table->Flags & ImGuiTableFlags_BordersOuterH)
|
||||
{
|
||||
inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size);
|
||||
inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size);
|
||||
inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Min.y, outer_col, border_size);
|
||||
inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Max.y, outer_col, border_size);
|
||||
}
|
||||
}
|
||||
if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y)
|
||||
@@ -2869,7 +2890,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table)
|
||||
// Draw bottom-most row border between it is above outer border.
|
||||
const float border_y = table->RowPosY2;
|
||||
if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y)
|
||||
inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size);
|
||||
inner_drawlist->AddLineH(table->BorderX1, table->BorderX2, border_y, table->BorderColorLight, border_size);
|
||||
}
|
||||
|
||||
inner_drawlist->PopClipRect();
|
||||
@@ -3174,7 +3195,7 @@ void ImGui::TableHeader(const char* label)
|
||||
if (label == NULL)
|
||||
label = "";
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
ImVec2 label_size = CalcTextSize(label, label_end, true);
|
||||
ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
ImVec2 label_pos = window->DC.CursorPos;
|
||||
|
||||
// If we already got a row height, there's use that.
|
||||
@@ -3293,6 +3314,8 @@ void ImGui::TableHeader(const char* label)
|
||||
// We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden
|
||||
if (IsPopupOpenRequestForItem(ImGuiPopupFlags_None, id))
|
||||
TableOpenContextMenu(column_n);
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
||||
}
|
||||
|
||||
// Unlike TableHeadersRow() it is not expected that you can reimplement or customize this with custom widgets.
|
||||
@@ -4595,7 +4618,7 @@ void ImGui::EndColumns()
|
||||
// Draw column
|
||||
const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
|
||||
const float xi = IM_TRUNC(x);
|
||||
window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col);
|
||||
window->DrawList->AddLineV(xi, y1 + 1.0f, y2, col);
|
||||
}
|
||||
|
||||
// Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
|
||||
|
||||
+263
-113
@@ -1,4 +1,4 @@
|
||||
// dear imgui, v1.92.7
|
||||
// dear imgui, v1.92.9 WIP
|
||||
// (widgets code)
|
||||
|
||||
/*
|
||||
@@ -92,6 +92,7 @@ Index of this file:
|
||||
#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1
|
||||
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
|
||||
#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers
|
||||
#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may change value
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result
|
||||
#endif
|
||||
|
||||
@@ -265,6 +266,8 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
|
||||
}
|
||||
}
|
||||
|
||||
// Note that all functions taking format strings in the API may be passed ("%s", text) or ("%.*s", text_len, text),
|
||||
// which will automatically bypass the formatter.
|
||||
void ImGui::TextUnformatted(const char* text, const char* text_end)
|
||||
{
|
||||
TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
|
||||
@@ -401,7 +404,8 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
|
||||
const char* value_text_begin, *value_text_end;
|
||||
ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
|
||||
const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
|
||||
const ImVec2 pos = window->DC.CursorPos;
|
||||
const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
|
||||
@@ -413,7 +417,7 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
|
||||
// Render
|
||||
RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
|
||||
if (label_size.x > 0.0f)
|
||||
RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
|
||||
RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label, label_end, false);
|
||||
}
|
||||
|
||||
void ImGui::BulletText(const char* fmt, ...)
|
||||
@@ -788,7 +792,8 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags
|
||||
ImGuiContext& g = *GImGui;
|
||||
const ImGuiStyle& style = g.Style;
|
||||
const ImGuiID id = window->GetID(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
|
||||
@@ -810,7 +815,7 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags
|
||||
|
||||
if (g.LogEnabled)
|
||||
LogSetNextTextDecoration("[", "]");
|
||||
RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
|
||||
RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, label_end, &label_size, style.ButtonTextAlign, &bb);
|
||||
|
||||
// Automatically close popups
|
||||
//if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
|
||||
@@ -981,7 +986,7 @@ ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
|
||||
const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
|
||||
IM_ASSERT(scrollbar_size >= 0.0f);
|
||||
const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f);
|
||||
const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f;
|
||||
const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : (window->Flags & ImGuiWindowFlags_NoTitleBar) ? border_size : 0;
|
||||
if (axis == ImGuiAxis_X)
|
||||
return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);
|
||||
else
|
||||
@@ -1155,7 +1160,7 @@ void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const Im
|
||||
else
|
||||
window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
|
||||
if (g.Style.ImageBorderSize > 0.0f)
|
||||
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), rounding, ImDrawFlags_None, g.Style.ImageBorderSize);
|
||||
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), rounding, g.Style.ImageBorderSize);
|
||||
}
|
||||
|
||||
void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1)
|
||||
@@ -1250,7 +1255,8 @@ bool ImGui::Checkbox(const char* label, bool* v)
|
||||
ImGuiContext& g = *GImGui;
|
||||
const ImGuiStyle& style = g.Style;
|
||||
const ImGuiID id = window->GetID(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
|
||||
const float square_sz = GetFrameHeight();
|
||||
const ImVec2 pos = window->DC.CursorPos;
|
||||
@@ -1291,8 +1297,9 @@ bool ImGui::Checkbox(const char* label, bool* v)
|
||||
if (is_visible)
|
||||
{
|
||||
RenderNavCursor(total_bb, id);
|
||||
RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
|
||||
ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : (mixed_value || checked) ? ImGuiCol_CheckboxSelectedBg : ImGuiCol_FrameBg);
|
||||
ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
|
||||
RenderFrame(check_bb.Min, check_bb.Max, bg_col, true, style.FrameRounding);
|
||||
if (mixed_value)
|
||||
{
|
||||
// Undocumented tristate/mixed/indeterminate checkbox (#2644)
|
||||
@@ -1310,7 +1317,7 @@ bool ImGui::Checkbox(const char* label, bool* v)
|
||||
if (g.LogEnabled)
|
||||
LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
|
||||
if (is_visible && label_size.x > 0.0f)
|
||||
RenderText(label_pos, label);
|
||||
RenderText(label_pos, label, label_end, false);
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
|
||||
return pressed;
|
||||
@@ -1372,7 +1379,8 @@ bool ImGui::RadioButton(const char* label, bool active)
|
||||
ImGuiContext& g = *GImGui;
|
||||
const ImGuiStyle& style = g.Style;
|
||||
const ImGuiID id = window->GetID(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
|
||||
const float square_sz = GetFrameHeight();
|
||||
const ImVec2 pos = window->DC.CursorPos;
|
||||
@@ -1411,7 +1419,7 @@ bool ImGui::RadioButton(const char* label, bool active)
|
||||
if (g.LogEnabled)
|
||||
LogRenderedText(&label_pos, active ? "(x)" : "( )");
|
||||
if (label_size.x > 0.0f)
|
||||
RenderText(label_pos, label);
|
||||
RenderText(label_pos, label, label_end, false);
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
||||
return pressed;
|
||||
@@ -1527,7 +1535,7 @@ bool ImGui::TextLink(const char* label)
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
|
||||
ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
|
||||
ImVec2 size = CalcTextSize(label, label_end, true);
|
||||
ImVec2 size = CalcTextSize(label, label_end, false);
|
||||
ImRect bb(pos, pos + size);
|
||||
ItemSize(size, 0.0f);
|
||||
if (!ItemAdd(bb, id))
|
||||
@@ -1558,10 +1566,10 @@ bool ImGui::TextLink(const char* label)
|
||||
}
|
||||
|
||||
float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f);
|
||||
window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf), 1.0f * (float)(int)g.Style._MainScale); // FIXME-TEXT: Underline mode // FIXME-DPI
|
||||
window->DrawList->AddLineH(bb.Min.x, bb.Max.x, line_y, GetColorU32(line_colf), 1.0f * (float)(int)g.Style._MainScale); // FIXME-TEXT: Underline mode // FIXME-DPI
|
||||
|
||||
PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));
|
||||
RenderText(bb.Min, label, label_end);
|
||||
RenderText(bb.Min, label, label_end, false);
|
||||
PopStyleColor();
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
||||
@@ -1731,7 +1739,7 @@ void ImGui::Separator()
|
||||
if (window->DC.CurrentColumns)
|
||||
flags |= ImGuiSeparatorFlags_SpanAllColumns;
|
||||
|
||||
SeparatorEx(flags, g.Style.SeparatorSize);
|
||||
SeparatorEx(flags, ImMax(g.Style.SeparatorSize, 1.0f));
|
||||
}
|
||||
|
||||
void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
|
||||
@@ -1747,14 +1755,14 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end
|
||||
const float separator_thickness = style.SeparatorTextBorderSize;
|
||||
const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness));
|
||||
const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
|
||||
const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f));
|
||||
const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f));
|
||||
ItemSize(min_size, text_baseline_y);
|
||||
if (!ItemAdd(bb, id))
|
||||
return;
|
||||
|
||||
const float sep1_x1 = pos.x;
|
||||
const float sep2_x2 = bb.Max.x;
|
||||
const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
|
||||
const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f);
|
||||
|
||||
const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f);
|
||||
const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN
|
||||
@@ -1768,9 +1776,9 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end
|
||||
const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
|
||||
const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
|
||||
if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
|
||||
window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness);
|
||||
window->DrawList->AddLineH(sep1_x1, sep1_x2, seps_y, separator_col, separator_thickness);
|
||||
if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
|
||||
window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
|
||||
window->DrawList->AddLineH(sep2_x1, sep2_x2, seps_y, separator_col, separator_thickness);
|
||||
if (g.LogEnabled)
|
||||
LogSetNextTextDecoration("---", NULL);
|
||||
RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size);
|
||||
@@ -1780,7 +1788,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end
|
||||
if (g.LogEnabled)
|
||||
LogText("---");
|
||||
if (separator_thickness > 0.0f)
|
||||
window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
|
||||
window->DrawList->AddLineH(sep1_x1, sep2_x2, seps_y, separator_col, separator_thickness);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1951,8 +1959,9 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF
|
||||
IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);
|
||||
|
||||
const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f;
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, false).x : 0.0f;
|
||||
const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
|
||||
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
|
||||
const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
||||
@@ -2003,7 +2012,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF
|
||||
RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
|
||||
}
|
||||
if (label_size.x > 0)
|
||||
RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);
|
||||
RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label, label_end, false);
|
||||
|
||||
if (!popup_open)
|
||||
return false;
|
||||
@@ -2376,12 +2385,17 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void
|
||||
|
||||
// Sanitize format
|
||||
// - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf
|
||||
// - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %.
|
||||
char format_sanitized[32];
|
||||
if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
||||
{
|
||||
format = type_info->ScanFmt;
|
||||
}
|
||||
else
|
||||
{
|
||||
format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_COUNTOF(format_sanitized));
|
||||
if (format[0] == '\0')
|
||||
format = type_info->ScanFmt; // Format doesn't want us to show the number currently, but we still need to parse the resulting input
|
||||
}
|
||||
|
||||
// Small types need a 32-bit buffer to receive the result from scanf()
|
||||
int v32 = 0;
|
||||
@@ -2716,7 +2730,8 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
|
||||
const float w = CalcItemWidth();
|
||||
const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0;
|
||||
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
|
||||
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
||||
|
||||
@@ -2792,7 +2807,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
|
||||
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
|
||||
|
||||
if (label_size.x > 0.0f)
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
|
||||
return value_changed;
|
||||
@@ -3317,7 +3332,8 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
|
||||
const float w = CalcItemWidth();
|
||||
const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0;
|
||||
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
|
||||
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
||||
|
||||
@@ -3389,7 +3405,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
|
||||
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
|
||||
|
||||
if (label_size.x > 0.0f)
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
|
||||
return value_changed;
|
||||
@@ -3494,7 +3510,8 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
|
||||
const ImGuiStyle& style = g.Style;
|
||||
const ImGuiID id = window->GetID(label);
|
||||
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
|
||||
const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
|
||||
|
||||
@@ -3539,8 +3556,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
|
||||
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
|
||||
RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f));
|
||||
if (label_size.x > 0.0f)
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
||||
return value_changed;
|
||||
}
|
||||
|
||||
@@ -3563,7 +3581,8 @@ bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min,
|
||||
// - ImParseFormatSanitizeForPrinting() [Internal]
|
||||
// - ImParseFormatSanitizeForScanning() [Internal]
|
||||
// - ImParseFormatPrecision() [Internal]
|
||||
// - TempInputTextScalar() [Internal]
|
||||
// - TempInputText() [Internal]
|
||||
// - TempInputScalar() [Internal]
|
||||
// - InputScalar()
|
||||
// - InputScalarN()
|
||||
// - InputFloat()
|
||||
@@ -3792,7 +3811,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
|
||||
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiStyle& style = g.Style;
|
||||
IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()!
|
||||
//IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()!
|
||||
|
||||
if (format == NULL)
|
||||
format = DataTypeGetInfo(data_type)->PrintFmt;
|
||||
@@ -3829,7 +3848,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
|
||||
}
|
||||
|
||||
// Apply
|
||||
bool value_changed = ret ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false;
|
||||
bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved.
|
||||
bool value_changed = input_edited ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false;
|
||||
|
||||
// Step buttons
|
||||
if (has_step_buttons)
|
||||
@@ -3843,13 +3863,13 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
|
||||
if (ButtonEx("-", ImVec2(button_size, button_size)))
|
||||
{
|
||||
DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
|
||||
value_changed = true;
|
||||
value_changed = ret = true;
|
||||
}
|
||||
SameLine(0, style.ItemInnerSpacing.x);
|
||||
if (ButtonEx("+", ImVec2(button_size, button_size)))
|
||||
{
|
||||
DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
|
||||
value_changed = true;
|
||||
value_changed = ret = true;
|
||||
}
|
||||
PopItemFlag();
|
||||
if (flags & ImGuiInputTextFlags_ReadOnly)
|
||||
@@ -3871,6 +3891,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
|
||||
if (value_changed)
|
||||
MarkItemEdited(g.LastItemData.ID);
|
||||
|
||||
if (flags & ImGuiInputTextFlags_EnterReturnsTrue)
|
||||
return ret;
|
||||
return value_changed;
|
||||
}
|
||||
|
||||
@@ -4520,6 +4542,9 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* sta
|
||||
callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
|
||||
callback_data.EventChar = (ImWchar)c;
|
||||
callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
|
||||
callback_data.CursorPos = state->Stb->cursor;
|
||||
callback_data.SelectionStart = state->Stb->select_start;
|
||||
callback_data.SelectionEnd = state->Stb->select_end;
|
||||
callback_data.UserData = user_data;
|
||||
if (callback(&callback_data) != 0)
|
||||
return false;
|
||||
@@ -4568,6 +4593,7 @@ void ImGui::InputTextDeactivateHook(ImGuiID id)
|
||||
ImGuiInputTextState* state = &g.InputTextState;
|
||||
if (id == 0 || state->ID != id)
|
||||
return;
|
||||
//IMGUI_DEBUG_LOG_ACTIVEID("InputTextDeactivateHook() id = 0x%08X\n", id);
|
||||
g.InputTextDeactivatedState.ID = state->ID;
|
||||
if (state->Flags & ImGuiInputTextFlags_ReadOnly)
|
||||
{
|
||||
@@ -4702,7 +4728,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar)
|
||||
BeginGroup();
|
||||
const ImGuiID id = window->GetID(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line
|
||||
const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
|
||||
|
||||
@@ -4716,7 +4743,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
{
|
||||
ImVec2 backup_pos = window->DC.CursorPos;
|
||||
ItemSize(total_bb, style.FramePadding.y);
|
||||
if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
|
||||
bool no_clip = (g.InputTextDeactivatedState.ID == id) || (g.ActiveId == id) || (id == g.NavActivateId); // Mimic some of ItemAdd() logic + add InputTextDeactivatedState.ID check.
|
||||
if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable) && !no_clip)
|
||||
{
|
||||
EndGroup();
|
||||
return false;
|
||||
@@ -4742,7 +4770,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
g.NavActivateId = backup_activate_id;
|
||||
PopStyleVar(3);
|
||||
PopStyleColor();
|
||||
if (!child_visible)
|
||||
if (!child_visible && !no_clip)
|
||||
{
|
||||
EndChild();
|
||||
EndGroup();
|
||||
@@ -4806,7 +4834,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
|
||||
|
||||
const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf);
|
||||
const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state.
|
||||
const bool init_changed_specs_multiline = (state != NULL && (state->Stb->single_line != !is_multiline)); // state != NULL means its our state.
|
||||
const bool init_changed_specs_readonly = (state != NULL && ((state->Flags ^ flags) & ImGuiInputTextFlags_ReadOnly)); // state != NULL means its our state.
|
||||
const bool init_make_active = (input_requested_by_user || input_requested_by_nav || input_requested_by_reactivate || user_scroll_finish);
|
||||
if (init_reload_from_user_buf)
|
||||
{
|
||||
@@ -4820,7 +4849,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
state->Stb->select_start = state->ReloadSelectionStart;
|
||||
state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; // will be clamped to bounds below
|
||||
}
|
||||
else if ((init_make_active && g.ActiveId != id) || init_changed_specs)
|
||||
else if ((init_make_active && g.ActiveId != id) || init_changed_specs_multiline || init_changed_specs_readonly)
|
||||
{
|
||||
// Access state even if we don't own it yet.
|
||||
state = &g.InputTextState;
|
||||
@@ -4841,8 +4870,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
|
||||
// Preserve cursor position and undo/redo stack if we come back to same widget
|
||||
// FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
|
||||
bool recycle_state = (state->ID == id && !init_changed_specs);
|
||||
if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0)))
|
||||
bool recycle_state = (state->ID == id && !init_changed_specs_multiline);
|
||||
if (recycle_state && !init_changed_specs_readonly && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0)))
|
||||
recycle_state = false;
|
||||
|
||||
// Start edition
|
||||
@@ -4880,7 +4909,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
}
|
||||
|
||||
const bool is_osx = io.ConfigMacOSXBehaviors;
|
||||
if (g.ActiveId != id && init_make_active)
|
||||
if (init_make_active && g.ActiveId != id)
|
||||
{
|
||||
IM_ASSERT(state && state->ID == id);
|
||||
SetActiveID(id, window);
|
||||
@@ -4951,13 +4980,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
if (is_password && !is_displaying_hint)
|
||||
PushPasswordFont();
|
||||
|
||||
// Word-wrapping: attempt to keep cursor in view while resizing frame/parent
|
||||
// FIXME-WORDWRAP: It would be better to preserve same relative offset.
|
||||
if (is_wordwrap && state != NULL && state->ID == id && state->WrapWidth != wrap_width)
|
||||
if (state != NULL && state->ID == id)
|
||||
{
|
||||
state->CursorCenterY = true;
|
||||
state->WrapWidth = wrap_width;
|
||||
render_cursor = true;
|
||||
state->Flags = flags;
|
||||
|
||||
// Word-wrapping: attempt to keep cursor in view while resizing frame/parent (FIXME-WORDWRAP: would be better to preserve same relative offset)
|
||||
if (is_wordwrap && state->WrapWidth != wrap_width)
|
||||
{
|
||||
state->CursorCenterY = true;
|
||||
state->WrapWidth = wrap_width;
|
||||
render_cursor = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Process mouse inputs and character inputs
|
||||
@@ -4966,7 +4999,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
IM_ASSERT(state != NULL);
|
||||
state->EditedThisFrame = false;
|
||||
state->BufCapacity = buf_size;
|
||||
state->Flags = flags;
|
||||
state->WrapWidth = wrap_width;
|
||||
|
||||
// Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
|
||||
@@ -5618,7 +5650,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
|
||||
ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f);
|
||||
if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
|
||||
draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f * (float)(int)style._MainScale); // FIXME-DPI: Cursor thickness (#7031)
|
||||
draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize);
|
||||
|
||||
// Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
|
||||
// This is required for some backends (SDL3) to start emitting character/text inputs.
|
||||
@@ -5666,7 +5698,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
||||
}
|
||||
|
||||
if (label_size.x > 0)
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
|
||||
|
||||
if (value_changed)
|
||||
MarkItemEdited(id);
|
||||
@@ -6332,7 +6364,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
|
||||
const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
|
||||
const int vert_start_idx = draw_list->VtxBuffer.Size;
|
||||
draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
|
||||
draw_list->PathStroke(col_white, 0, wheel_thickness);
|
||||
draw_list->PathStroke(col_white, wheel_thickness);
|
||||
const int vert_end_idx = draw_list->VtxBuffer.Size;
|
||||
|
||||
// Paint colors over existing vertices
|
||||
@@ -6476,7 +6508,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl
|
||||
if (g.Style.FrameBorderSize > 0.0f)
|
||||
RenderFrameBorder(bb.Min, bb.Max, rounding);
|
||||
else
|
||||
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding, 0, 1.0f * (float)(int)g.Style._MainScale); // Color buttons are often in need of some sort of border // FIXME-DPI
|
||||
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding, 1.0f * (float)(int)g.Style._MainScale); // Color buttons are often in need of some sort of border // FIXME-DPI
|
||||
}
|
||||
|
||||
// Drag and Drop Source
|
||||
@@ -7149,11 +7181,11 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos)
|
||||
window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3);
|
||||
if (x1 < x2)
|
||||
window->DrawList->PathLineTo(ImVec2(x2, y));
|
||||
window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), ImDrawFlags_None, g.Style.TreeLinesSize);
|
||||
window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
|
||||
window->DrawList->AddLineH(x1, x2, y, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7179,7 +7211,7 @@ void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data)
|
||||
float x = ImTrunc(data->DrawLinesX1);
|
||||
if (data->DrawLinesTableColumn != -1)
|
||||
TablePushColumnChannel(data->DrawLinesTableColumn);
|
||||
window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
|
||||
window->DrawList->AddLineV(x, y1, y2, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
|
||||
if (data->DrawLinesTableColumn != -1)
|
||||
TablePopColumnChannel();
|
||||
}
|
||||
@@ -7338,7 +7370,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
||||
|
||||
// Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
|
||||
ImGuiID id = window->GetID(label);
|
||||
ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
pos.y += window->DC.CurrLineTextBaseOffset;
|
||||
@@ -7493,7 +7526,11 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
||||
|
||||
// Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns.
|
||||
if (is_visible)
|
||||
RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb);
|
||||
RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, label_end, &label_size, style.SelectableTextAlign, &bb);
|
||||
|
||||
#ifdef IMGUI_DEBUG_BOXSELECT
|
||||
if (g.BoxSelectState.UnclipMode) { GetForegroundDrawList()->AddText(pos, IM_COL32(255,255,0,200), label, label_end); }
|
||||
#endif
|
||||
|
||||
// Automatically close popups
|
||||
if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))
|
||||
@@ -7810,7 +7847,7 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI
|
||||
return false;
|
||||
|
||||
// Current frame absolute prev/current rectangles are used to toggle selection.
|
||||
// They are derived from positions relative to scrolling space.
|
||||
// They are derived from positions relative to scrolling space, so "previous" rectangle is reprojected for current frame coordinates.
|
||||
ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);
|
||||
ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already
|
||||
ImVec2 curr_end_pos_abs = g.IO.MousePos;
|
||||
@@ -7820,20 +7857,69 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI
|
||||
bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
|
||||
bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
|
||||
bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
|
||||
//IMGUI_DEBUG_LOG("StartPosRel (%.2f,%.2f) EndPosRel (%.2f,%.2f) -> (%.2f,%.2f)\n", bs->StartPosRel.x, bs->StartPosRel.y, bs->EndPosRel.x, bs->EndPosRel.y, WindowPosAbsToRel(window, g.IO.MousePos).x, WindowPosAbsToRel(window, g.IO.MousePos).y);
|
||||
|
||||
// Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper)
|
||||
// Storing an extra rect used by widgets supporting box-select.
|
||||
if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
|
||||
if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
|
||||
// Box-select 2D mode detects change of the rectangle.
|
||||
// Storing unclip rects which will be tested by widgets supporting box-select. Always update rectangles when active (even if we don't use them).
|
||||
// To facilitate understanding this: enable IMGUI_DEBUG_BOXSELECT and visualize all geometry.
|
||||
if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
|
||||
{
|
||||
// For both sides, compute the area differing between Prev and Curr rectangles.
|
||||
bs->UnclipRects[0] = bs->UnclipRects[1] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||
for (int side = 0; side < 2; side++)
|
||||
{
|
||||
bs->UnclipMode = true;
|
||||
bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis.
|
||||
bs->UnclipRect.Add(bs->BoxSelectRectCurr);
|
||||
ImVec2 d_min = (side == 0) ? ImMin(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMin(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max);
|
||||
ImVec2 d_max = (side == 0) ? ImMax(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMax(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max);
|
||||
if (d_min.x != d_max.x)
|
||||
{
|
||||
bs->UnclipRects[0].AddX(d_min.x);
|
||||
bs->UnclipRects[0].AddX(d_max.x);
|
||||
}
|
||||
if (d_min.y != d_max.y)
|
||||
{
|
||||
bs->UnclipRects[1].AddY(d_min.y);
|
||||
bs->UnclipRects[1].AddY(d_max.y);
|
||||
}
|
||||
}
|
||||
|
||||
//GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
|
||||
ImRect box_select_intersection = bs->BoxSelectRectPrev;
|
||||
box_select_intersection.Add(bs->BoxSelectRectCurr);
|
||||
if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
|
||||
if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
|
||||
{
|
||||
bs->UnclipRects[0].AddY(box_select_intersection.Min.y);
|
||||
bs->UnclipRects[0].AddY(box_select_intersection.Max.y);
|
||||
}
|
||||
if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
|
||||
if (bs->BoxSelectRectPrev.Min.y != bs->BoxSelectRectCurr.Min.y || bs->BoxSelectRectPrev.Max.y != bs->BoxSelectRectCurr.Max.y)
|
||||
{
|
||||
bs->UnclipRects[1].AddX(box_select_intersection.Min.x);
|
||||
bs->UnclipRects[1].AddX(box_select_intersection.Max.x);
|
||||
}
|
||||
|
||||
// Merge both rectangles into one.
|
||||
// FIXME-OPT: When UnclipRect.Area() is much larger than the sum of UnclipRects[0]/[1] Areas, widgets should
|
||||
// ideally first use UnclipRect as a first coarse cull layer + the individual ones as a second validation.
|
||||
bs->UnclipRect = bs->UnclipRects[0];
|
||||
bs->UnclipRect.Add(bs->UnclipRects[1]);
|
||||
if (!bs->UnclipRect.IsInverted() && (!window->ClipRect.Contains(bs->UnclipRect.Min) || !window->ClipRect.Contains(bs->UnclipRect.Max))) // !! Don't use Contains(ImRect)
|
||||
bs->UnclipMode = true;
|
||||
if (bs->UnclipMode && g.CurrentTable != NULL)
|
||||
TableApplyExternalUnclipRect(g.CurrentTable, bs->UnclipRect); // No need submitting both
|
||||
}
|
||||
|
||||
#ifdef IMGUI_DEBUG_BOXSELECT
|
||||
//GetForegroundDrawList()->AddRect(scope_rect.Min, scope_rect.Max, IM_COL32(0, 255, 0, 200), 0.0f, 0, 4.0f);
|
||||
//GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
|
||||
//GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);
|
||||
if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
|
||||
{
|
||||
for (ImRect& unclip_r : bs->UnclipRects)
|
||||
if (!unclip_r.IsInverted())
|
||||
GetForegroundDrawList()->AddRect(unclip_r.Min, unclip_r.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 4.0f);
|
||||
GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 2.0f);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7849,8 +7935,9 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag
|
||||
bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view
|
||||
ImRect box_select_r = bs->BoxSelectRectCurr;
|
||||
box_select_r.ClipWith(scope_rect);
|
||||
window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
|
||||
window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling
|
||||
ImGuiWindow* draw_window = FindFrontMostVisibleChildWindow(window);
|
||||
draw_window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
|
||||
draw_window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling
|
||||
|
||||
// Scroll
|
||||
const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;
|
||||
@@ -7890,18 +7977,18 @@ static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSe
|
||||
|
||||
static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
|
||||
{
|
||||
// Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only
|
||||
// This probably doesn't work inside a table as there are ample ambiguities related to exact time of calling BeginMultiSelect()/EndMultiSelect().
|
||||
return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin));
|
||||
}
|
||||
else
|
||||
{
|
||||
// When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970)
|
||||
//// When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970)
|
||||
ImRect scope_rect = window->InnerClipRect;
|
||||
if (g.CurrentTable != NULL)
|
||||
scope_rect = g.CurrentTable->HostClipRect;
|
||||
//if (g.CurrentTable != NULL)
|
||||
// scope_rect = g.CurrentTable->HostClipRect;
|
||||
|
||||
// Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect?
|
||||
scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max);
|
||||
@@ -7937,18 +8024,24 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel
|
||||
// FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250)
|
||||
// They should perhaps be stacked properly?
|
||||
if (ImGuiTable* table = g.CurrentTable)
|
||||
if (table->CurrentColumn != -1)
|
||||
{
|
||||
if (!table->IsLayoutLocked)
|
||||
TableUpdateLayout(table);
|
||||
else if (table->CurrentColumn != -1)
|
||||
TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it.
|
||||
}
|
||||
|
||||
// FIXME: BeginFocusScope()
|
||||
const ImGuiID id = window->IDStack.back();
|
||||
ms->Clear();
|
||||
ms->FocusScopeId = id;
|
||||
ms->Flags = flags;
|
||||
ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId);
|
||||
ms->BackupCursorMaxPos = window->DC.CursorMaxPos;
|
||||
ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos;
|
||||
ms->ScopeRectMin = window->DC.CursorPos;
|
||||
if (flags & ImGuiMultiSelectFlags_ScopeRect)
|
||||
window->DC.CursorMaxPos = ms->ScopeRectMin; // CalcScopeRect() for ImGuiMultiSelectFlags_ScopeRect will measure in EndMultiSelect().
|
||||
PushFocusScope(ms->FocusScopeId);
|
||||
ms->IsFocused = IsInNavFocusRoute(g.CurrentFocusScopeId);
|
||||
if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items.
|
||||
window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main;
|
||||
|
||||
@@ -8030,7 +8123,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel
|
||||
storage->LastSelectionSize = 0;
|
||||
}
|
||||
ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1;
|
||||
ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid;
|
||||
//ms->PrevSubmittedItem = ImGuiSelectionUserData_Invalid;
|
||||
|
||||
if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
|
||||
DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO);
|
||||
@@ -8076,7 +8169,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
|
||||
// Clear selection when clicking void?
|
||||
// We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection!
|
||||
// The InnerRect test is necessary for non-child/decorated windows.
|
||||
bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos);
|
||||
bool scope_hovered = window->InnerRect.Contains(g.IO.MousePos) && IsWindowHovered(ImGuiHoveredFlags_ChildWindows);
|
||||
if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect))
|
||||
scope_hovered &= scope_rect.Contains(g.IO.MousePos);
|
||||
if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0)
|
||||
@@ -8102,10 +8195,13 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
|
||||
if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX)
|
||||
{
|
||||
IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope
|
||||
ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);
|
||||
NavMoveRequestTryWrapping(GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);
|
||||
}
|
||||
|
||||
// Unwind
|
||||
if (ImGuiTable* table = g.CurrentTable)
|
||||
if (table->IsInsideRow)
|
||||
TableEndRow(table);
|
||||
window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos);
|
||||
PopFocusScope();
|
||||
|
||||
@@ -8133,6 +8229,8 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d
|
||||
g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
|
||||
if (ms->IO.RangeSrcItem == selection_user_data)
|
||||
ms->RangeSrcPassedBy = true;
|
||||
//ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select.
|
||||
//ms->CurrSubmittedItem = selection_user_data;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -8281,8 +8379,31 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
|
||||
if (ms->BoxSelectId != 0)
|
||||
if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId))
|
||||
{
|
||||
const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect);
|
||||
const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect);
|
||||
ImRect item_rect = g.LastItemData.Rect;
|
||||
if (!window->DC.NavIsScrollPushableX) // FIXME: Rename to be more generic.
|
||||
if (ImGuiTable* table = g.CurrentTable)
|
||||
if (table->CurrentColumn != -1)
|
||||
{
|
||||
// FIXME: We cannot solely use current ClipRect as it includes HostClipRect.
|
||||
// However we account for ClipRect being larger than current column (e.g. when using SpanAllColumns)
|
||||
// A more generic version would be nice, but window->WorkRect.Min/Max exclude CellPadding. (#7994, #9383)
|
||||
ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];
|
||||
float clip_min_x = (g.LastItemData.ItemFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect.Min.x : window->ClipRect.Min.x;
|
||||
float clip_max_x = (g.LastItemData.ItemFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect.Max.x : window->ClipRect.Max.x;
|
||||
if (clip_min_x != clip_max_x) // When zero sized we expect that bounds have been clamped and thus are unreliable
|
||||
{
|
||||
item_rect.Min.x = ImMax(item_rect.Min.x, ImMin(column->MinX, clip_min_x));
|
||||
item_rect.Max.x = ImMin(item_rect.Max.x, ImMax(column->MaxX, clip_max_x));
|
||||
}
|
||||
else
|
||||
{
|
||||
item_rect.Min.x = ImMax(item_rect.Min.x, column->MinX);
|
||||
item_rect.Max.x = ImMin(item_rect.Max.x, column->MaxX);
|
||||
}
|
||||
//GetForegroundDrawList()->AddRect(item_rect.Min, item_rect.Max, IM_COL32(255, 0, 255, 255));
|
||||
}
|
||||
const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(item_rect);
|
||||
const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(item_rect);
|
||||
if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr))
|
||||
{
|
||||
if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce)
|
||||
@@ -8294,6 +8415,9 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
|
||||
{
|
||||
selected = !selected;
|
||||
MultiSelectAddSetRange(ms, selected, +1, item_data, item_data);
|
||||
#ifdef IMGUI_DEBUG_BOXSELECT
|
||||
GetForegroundDrawList()->AddRectFilled(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, selected ? IM_COL32(0, 255, 0, 200) : IM_COL32(255, 0, 0, 200));
|
||||
#endif
|
||||
}
|
||||
storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1);
|
||||
}
|
||||
@@ -8407,7 +8531,6 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
|
||||
}
|
||||
if (storage->NavIdItem == item_data)
|
||||
ms->NavIdPassedBy = true;
|
||||
ms->LastSubmittedItem = item_data;
|
||||
|
||||
*p_selected = selected;
|
||||
*p_pressed = pressed;
|
||||
@@ -8423,15 +8546,20 @@ void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected)
|
||||
void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item)
|
||||
{
|
||||
// Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges)
|
||||
// FIXME-OPT: Disabled on 2026/04/09 as this would break with any form of coarse clipping that we don't know about (e.g. TableNextColumn() return value).
|
||||
// The low-hanging fruit would be to know that ImGuiSelectionUserData are sequential indices, in which case we can trivially compare PrevSubmittedItem + RangeDir == FirstItem.
|
||||
// User can always perform this merge if required.
|
||||
#if 0
|
||||
if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0)
|
||||
{
|
||||
ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1];
|
||||
if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected)
|
||||
if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->PrevSubmittedItem && prev->Selected == selected)
|
||||
{
|
||||
prev->RangeLastItem = last_item;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item };
|
||||
ms->IO.Requests.push_back(req); // Add new request
|
||||
@@ -8669,7 +8797,8 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
|
||||
|
||||
const ImGuiStyle& style = g.Style;
|
||||
const ImGuiID id = GetID(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
|
||||
// Size default to hold ~7.25 items.
|
||||
// Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
|
||||
@@ -8692,7 +8821,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
|
||||
if (label_size.x > 0.0f)
|
||||
{
|
||||
ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
|
||||
RenderText(label_pos, label);
|
||||
RenderText(label_pos, label, label_end, false);
|
||||
window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
|
||||
AlignTextToFramePadding();
|
||||
}
|
||||
@@ -8773,9 +8902,7 @@ bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)(
|
||||
// - PlotHistogram()
|
||||
//-------------------------------------------------------------------------
|
||||
// Plot/Graph widgets are not very good.
|
||||
// Consider writing your own, or using a third-party one, see:
|
||||
// - ImPlot https://github.com/epezent/implot
|
||||
// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
|
||||
// Consider using ImPlot (https://github.com/epezent/implot) which is much better!
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg)
|
||||
@@ -8788,7 +8915,8 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get
|
||||
const ImGuiStyle& style = g.Style;
|
||||
const ImGuiID id = window->GetID(label);
|
||||
|
||||
const ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
const ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);
|
||||
|
||||
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
|
||||
@@ -8831,7 +8959,7 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get
|
||||
// Tooltip on hover
|
||||
if (hovered && inner_bb.Contains(g.IO.MousePos))
|
||||
{
|
||||
const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
|
||||
const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.999f);
|
||||
const int v_idx = (int)(t * item_count);
|
||||
IM_ASSERT(v_idx >= 0 && v_idx < values_count);
|
||||
|
||||
@@ -8887,10 +9015,11 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get
|
||||
RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f));
|
||||
|
||||
if (label_size.x > 0.0f)
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label, label_end, false);
|
||||
|
||||
// Return hovered index or -1 if none are hovered.
|
||||
// This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx().
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
|
||||
return idx_hovered;
|
||||
}
|
||||
|
||||
@@ -8920,6 +9049,7 @@ void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int
|
||||
PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
|
||||
}
|
||||
|
||||
// Plot Histogram (the data provided _is_ histogram data. it doesn't compute the histogram of your data)
|
||||
void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
|
||||
{
|
||||
ImGuiPlotArrayGetterData data(values, stride);
|
||||
@@ -9257,7 +9387,8 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
||||
// Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
|
||||
g.MenusIdSubmittedThisFrame.push_back(id);
|
||||
|
||||
ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
|
||||
// Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window)
|
||||
// This is only done for items for the menu set and not the full parent window.
|
||||
@@ -9276,8 +9407,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
||||
|
||||
bool pressed;
|
||||
|
||||
// We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
|
||||
const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;
|
||||
const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoAutoClosePopups | (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnClick;
|
||||
ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
|
||||
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
|
||||
{
|
||||
@@ -9289,7 +9419,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
||||
ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, pos.y + window->DC.CurrLineTextBaseOffset);
|
||||
pressed = Selectable("", menu_is_open, selectable_flags, label_size);
|
||||
LogSetNextTextDecoration("[", "]");
|
||||
RenderText(text_pos, label);
|
||||
RenderText(text_pos, label, label_end, false);
|
||||
PopStyleVar();
|
||||
window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
|
||||
popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), text_pos.y - style.FramePadding.y + window->MenuBarHeight);
|
||||
@@ -9306,7 +9436,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
||||
ImVec2 text_pos(window->DC.CursorPos.x, pos.y + window->DC.CurrLineTextBaseOffset);
|
||||
pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
|
||||
LogSetNextTextDecoration("", ">");
|
||||
RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label);
|
||||
RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label, label_end, false);
|
||||
if (icon_w > 0.0f)
|
||||
RenderText(ImVec2(text_pos.x + offsets->OffsetIcon, text_pos.y), icon);
|
||||
RenderArrow(window->DrawList, ImVec2(text_pos.x + offsets->OffsetMark + extra_w + g.FontSize * 0.30f, text_pos.y), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
|
||||
@@ -9315,6 +9445,14 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
||||
if (!enabled)
|
||||
EndDisabled();
|
||||
|
||||
// Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394)
|
||||
// Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here.
|
||||
if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0))
|
||||
{
|
||||
ClearActiveID();
|
||||
SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner);
|
||||
}
|
||||
|
||||
const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav;
|
||||
if (menuset_is_open)
|
||||
PopItemFlag();
|
||||
@@ -9395,6 +9533,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
|
||||
PopID();
|
||||
|
||||
if (g.ActiveId == id && want_open)
|
||||
g.ActiveIdNoClearOnFocusLoss = true;
|
||||
|
||||
if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
|
||||
{
|
||||
// Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame.
|
||||
@@ -9471,7 +9612,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiStyle& style = g.Style;
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
|
||||
// See BeginMenuEx() for comments about this.
|
||||
const bool menuset_is_open = IsRootOfOpenMenuSet();
|
||||
@@ -9486,7 +9628,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
|
||||
BeginDisabled();
|
||||
|
||||
// We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
|
||||
const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover;
|
||||
const ImGuiSelectableFlags selectable_flags = (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnRelease | (ImGuiSelectableFlags)ImGuiSelectableFlags_SetNavIdOnHover;
|
||||
ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
|
||||
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
|
||||
{
|
||||
@@ -9498,7 +9640,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
|
||||
pressed = Selectable("", selected, selectable_flags, ImVec2(label_size.x, 0.0f));
|
||||
PopStyleVar();
|
||||
if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
|
||||
RenderText(text_pos, label);
|
||||
RenderText(text_pos, label, label_end, false);
|
||||
window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
|
||||
}
|
||||
else
|
||||
@@ -9515,7 +9657,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
|
||||
pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
|
||||
if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
|
||||
{
|
||||
RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
|
||||
RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label, label_end, false);
|
||||
if (icon_w > 0.0f)
|
||||
RenderText(text_pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
|
||||
if (shortcut_w > 0.0f)
|
||||
@@ -9529,6 +9671,17 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
|
||||
RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
|
||||
}
|
||||
}
|
||||
|
||||
// Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394)
|
||||
// Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here.
|
||||
const ImGuiID id = g.LastItemData.ID;
|
||||
if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0))
|
||||
{
|
||||
ClearActiveID();
|
||||
SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner);
|
||||
}
|
||||
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
|
||||
if (!enabled)
|
||||
EndDisabled();
|
||||
@@ -10616,7 +10769,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
||||
// We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)
|
||||
const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
|
||||
if (want_clip_rect)
|
||||
PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
|
||||
PushClipRect(ImVec2(ImClamp(bb.Min.x, tab_bar->ScrollingRectMinX, tab_bar->ScrollingRectMaxX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
|
||||
|
||||
ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
|
||||
ItemSize(bb.GetSize(), style.FramePadding.y);
|
||||
@@ -10740,7 +10893,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
||||
float rounding = style.TabRounding;
|
||||
display_draw_list->PathArcToFast(tl + ImVec2(+rounding, +rounding), rounding, 7, 9);
|
||||
display_draw_list->PathArcToFast(tr + ImVec2(-rounding, +rounding), rounding, 9, 11);
|
||||
display_draw_list->PathStroke(overline_col, 0, style.TabBarOverlineSize);
|
||||
display_draw_list->PathStroke(overline_col, style.TabBarOverlineSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -10845,20 +10998,16 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI
|
||||
IM_UNUSED(flags);
|
||||
IM_ASSERT(width > 0.0f);
|
||||
const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
|
||||
const float y1 = bb.Min.y + 1.0f;
|
||||
const float y1 = bb.Min.y + 1.0f; // Leave a bit of room in title bars.
|
||||
const float y2 = bb.Max.y - g.Style.TabBarBorderSize;
|
||||
draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
|
||||
draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
|
||||
draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
|
||||
draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
|
||||
draw_list->PathFillConvex(col);
|
||||
draw_list->AddRectFilled(bb.Min, ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundCornersTop);
|
||||
if (g.Style.TabBorderSize > 0.0f)
|
||||
{
|
||||
draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
|
||||
draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
|
||||
draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
|
||||
draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
|
||||
draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
|
||||
draw_list->PathStroke(GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10867,7 +11016,8 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI
|
||||
void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImVec2 label_size = CalcTextSize(label, NULL, true);
|
||||
const char* label_end = FindRenderedTextEnd(label);
|
||||
ImVec2 label_size = CalcTextSize(label, label_end, false);
|
||||
|
||||
if (out_just_closed)
|
||||
*out_just_closed = false;
|
||||
@@ -10952,7 +11102,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
|
||||
}
|
||||
}
|
||||
LogSetNextTextDecoration("/", "\\");
|
||||
RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, NULL, &label_size);
|
||||
RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, label_end, &label_size);
|
||||
|
||||
#if 0
|
||||
if (!is_contents_visible)
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
# Minimal build for the vendored libjpeg-turbo.
|
||||
# Upstream's CMake pulls in Java bindings, tools, tests and packaging and sets
|
||||
# its own global flags, none of which survive contact with this project's four
|
||||
# cross toolchains, so only the static library is described here.
|
||||
|
||||
set(LJT_VERSION "3.0.4")
|
||||
set(VERSION ${LJT_VERSION})
|
||||
set(JPEG_LIB_VERSION 62)
|
||||
set(LIBJPEG_TURBO_VERSION_NUMBER 3000004)
|
||||
set(BUILD "spice2x")
|
||||
|
||||
set(C_ARITH_CODING_SUPPORTED 1)
|
||||
set(D_ARITH_CODING_SUPPORTED 1)
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(LJT_ARCH "x86_64")
|
||||
else()
|
||||
set(LJT_ARCH "i386")
|
||||
endif()
|
||||
|
||||
# SIMD is the entire reason for taking this dependency, but a missing assembler
|
||||
# must not break the build; the C paths are still correct, just slower.
|
||||
# ASM_NASM is enabled by the top level CMakeLists.
|
||||
if(CMAKE_ASM_NASM_COMPILER)
|
||||
set(WITH_SIMD 1)
|
||||
else()
|
||||
message(WARNING "nasm not found, building libjpeg-turbo without SIMD (screen capture will be slower)")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
set(HIDDEN "")
|
||||
set(INLINE "__forceinline")
|
||||
set(THREAD_LOCAL "__declspec(thread)")
|
||||
set(HAVE_INTRIN_H 1)
|
||||
else()
|
||||
set(HIDDEN "__attribute__((visibility(\"hidden\")))")
|
||||
set(INLINE "inline __attribute__((always_inline))")
|
||||
set(THREAD_LOCAL "__thread")
|
||||
set(HAVE_BUILTIN_CTZL 1)
|
||||
endif()
|
||||
|
||||
set(SIZE_T ${CMAKE_SIZEOF_VOID_P})
|
||||
|
||||
configure_file(jconfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/jconfig.h)
|
||||
configure_file(jconfigint.h.in ${CMAKE_CURRENT_BINARY_DIR}/jconfigint.h)
|
||||
configure_file(jversion.h.in ${CMAKE_CURRENT_BINARY_DIR}/jversion.h)
|
||||
|
||||
# libjpeg-turbo 3.x supports 8, 12 and 16 bit samples by compiling the
|
||||
# precision dependent units once per precision; jcinit.c dispatches to all
|
||||
# three at runtime, so none of them can be dropped.
|
||||
# jstdhuff.c and the *ext.c units are textually included and never compiled.
|
||||
set(JPEG16_SOURCES
|
||||
jcapistd.c jccolor.c jcdiffct.c jclossls.c jcmainct.c jcprepct.c
|
||||
jcsample.c jdapistd.c jdcolor.c jddiffct.c jdlossls.c jdmainct.c
|
||||
jdpostct.c jdsample.c jutils.c)
|
||||
|
||||
set(JPEG12_SOURCES ${JPEG16_SOURCES}
|
||||
jccoefct.c jcdctmgr.c jdcoefct.c jddctmgr.c jdmerge.c jfdctfst.c
|
||||
jfdctint.c jidctflt.c jidctfst.c jidctint.c jidctred.c jquant1.c
|
||||
jquant2.c)
|
||||
|
||||
set(JPEG_SOURCES ${JPEG12_SOURCES}
|
||||
jcapimin.c jchuff.c jcicc.c jcinit.c jclhuff.c jcmarker.c jcmaster.c
|
||||
jcomapi.c jcparam.c jcphuff.c jctrans.c jdapimin.c jdatadst.c
|
||||
jdatasrc.c jdhuff.c jdicc.c jdinput.c jdlhuff.c jdmarker.c jdmaster.c
|
||||
jdphuff.c jdtrans.c jerror.c jfdctflt.c jmemmgr.c jmemnobs.c
|
||||
jpeg_nbits.c
|
||||
jaricom.c jcarith.c jdarith.c)
|
||||
|
||||
if(WITH_SIMD)
|
||||
# the *ext-*.asm units are textually included by these, so the list is
|
||||
# explicit rather than globbed
|
||||
if(LJT_ARCH STREQUAL "x86_64")
|
||||
set(LJT_SIMD_ASM
|
||||
x86_64/jsimdcpu.asm x86_64/jfdctflt-sse.asm
|
||||
x86_64/jccolor-sse2.asm x86_64/jcgray-sse2.asm x86_64/jchuff-sse2.asm
|
||||
x86_64/jcphuff-sse2.asm x86_64/jcsample-sse2.asm x86_64/jdcolor-sse2.asm
|
||||
x86_64/jdmerge-sse2.asm x86_64/jdsample-sse2.asm x86_64/jfdctfst-sse2.asm
|
||||
x86_64/jfdctint-sse2.asm x86_64/jidctflt-sse2.asm x86_64/jidctfst-sse2.asm
|
||||
x86_64/jidctint-sse2.asm x86_64/jidctred-sse2.asm x86_64/jquantf-sse2.asm
|
||||
x86_64/jquanti-sse2.asm
|
||||
x86_64/jccolor-avx2.asm x86_64/jcgray-avx2.asm x86_64/jcsample-avx2.asm
|
||||
x86_64/jdcolor-avx2.asm x86_64/jdmerge-avx2.asm x86_64/jdsample-avx2.asm
|
||||
x86_64/jfdctint-avx2.asm x86_64/jidctint-avx2.asm x86_64/jquanti-avx2.asm)
|
||||
else()
|
||||
set(LJT_SIMD_ASM
|
||||
i386/jsimdcpu.asm i386/jfdctflt-3dn.asm
|
||||
i386/jidctflt-3dn.asm i386/jquant-3dn.asm
|
||||
i386/jccolor-mmx.asm i386/jcgray-mmx.asm i386/jcsample-mmx.asm
|
||||
i386/jdcolor-mmx.asm i386/jdmerge-mmx.asm i386/jdsample-mmx.asm
|
||||
i386/jfdctfst-mmx.asm i386/jfdctint-mmx.asm i386/jidctfst-mmx.asm
|
||||
i386/jidctint-mmx.asm i386/jidctred-mmx.asm i386/jquant-mmx.asm
|
||||
i386/jfdctflt-sse.asm i386/jidctflt-sse.asm i386/jquant-sse.asm
|
||||
i386/jccolor-sse2.asm i386/jcgray-sse2.asm i386/jchuff-sse2.asm
|
||||
i386/jcphuff-sse2.asm i386/jcsample-sse2.asm i386/jdcolor-sse2.asm
|
||||
i386/jdmerge-sse2.asm i386/jdsample-sse2.asm i386/jfdctfst-sse2.asm
|
||||
i386/jfdctint-sse2.asm i386/jidctflt-sse2.asm i386/jidctfst-sse2.asm
|
||||
i386/jidctint-sse2.asm i386/jidctred-sse2.asm i386/jquantf-sse2.asm
|
||||
i386/jquanti-sse2.asm
|
||||
i386/jccolor-avx2.asm i386/jcgray-avx2.asm i386/jcsample-avx2.asm
|
||||
i386/jdcolor-avx2.asm i386/jdmerge-avx2.asm i386/jdsample-avx2.asm
|
||||
i386/jfdctint-avx2.asm i386/jidctint-avx2.asm i386/jquanti-avx2.asm)
|
||||
endif()
|
||||
|
||||
list(TRANSFORM LJT_SIMD_ASM PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/simd/")
|
||||
list(APPEND JPEG_SOURCES simd/${LJT_ARCH}/jsimd.c ${LJT_SIMD_ASM})
|
||||
|
||||
# nasm resolves %include against these, and requires the trailing separator
|
||||
set(CMAKE_ASM_NASM_FLAGS
|
||||
"${CMAKE_ASM_NASM_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/simd/nasm/ -I${CMAKE_CURRENT_SOURCE_DIR}/simd/${LJT_ARCH}/")
|
||||
|
||||
if(LJT_ARCH STREQUAL "x86_64")
|
||||
set(CMAKE_ASM_NASM_OBJECT_FORMAT win64)
|
||||
set(CMAKE_ASM_NASM_FLAGS "${CMAKE_ASM_NASM_FLAGS} -D__x86_64__ -DWIN64")
|
||||
else()
|
||||
set(CMAKE_ASM_NASM_OBJECT_FORMAT win32)
|
||||
set(CMAKE_ASM_NASM_FLAGS "${CMAKE_ASM_NASM_FLAGS} -DWIN32")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(jpeg12-static OBJECT ${JPEG12_SOURCES})
|
||||
set_property(TARGET jpeg12-static PROPERTY COMPILE_FLAGS "-DBITS_IN_JSAMPLE=12")
|
||||
|
||||
add_library(jpeg16-static OBJECT ${JPEG16_SOURCES})
|
||||
set_property(TARGET jpeg16-static PROPERTY COMPILE_FLAGS "-DBITS_IN_JSAMPLE=16")
|
||||
|
||||
add_library(jpeg-static STATIC ${JPEG_SOURCES}
|
||||
$<TARGET_OBJECTS:jpeg12-static>
|
||||
$<TARGET_OBJECTS:jpeg16-static>)
|
||||
|
||||
foreach(ljt_target jpeg-static jpeg12-static jpeg16-static)
|
||||
target_include_directories(${ljt_target} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_include_directories(${ljt_target} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simd
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simd/${LJT_ARCH})
|
||||
|
||||
if(NOT MSVC)
|
||||
target_compile_options(${ljt_target} PRIVATE -Wno-unused-parameter -Wno-sign-compare)
|
||||
endif()
|
||||
endforeach()
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
libjpeg-turbo Licenses
|
||||
======================
|
||||
|
||||
libjpeg-turbo is covered by two compatible BSD-style open source licenses:
|
||||
|
||||
- The IJG (Independent JPEG Group) License, which is listed in
|
||||
[README.ijg](README.ijg)
|
||||
|
||||
This license applies to the libjpeg API library and associated programs,
|
||||
including any code inherited from libjpeg and any modifications to that
|
||||
code. Note that the libjpeg-turbo SIMD source code bears the
|
||||
[zlib License](https://opensource.org/licenses/Zlib), but in the context of
|
||||
the overall libjpeg API library, the terms of the zlib License are subsumed
|
||||
by the terms of the IJG License.
|
||||
|
||||
- The Modified (3-clause) BSD License, which is listed below
|
||||
|
||||
This license applies to the TurboJPEG API library and associated programs, as
|
||||
well as the build system. Note that the TurboJPEG API library wraps the
|
||||
libjpeg API library, so in the context of the overall TurboJPEG API library,
|
||||
both the terms of the IJG License and the terms of the Modified (3-clause)
|
||||
BSD License apply.
|
||||
|
||||
|
||||
Complying with the libjpeg-turbo Licenses
|
||||
=========================================
|
||||
|
||||
This section provides a roll-up of the libjpeg-turbo licensing terms, to the
|
||||
best of our understanding. This is not a license in and of itself. It is
|
||||
intended solely for clarification.
|
||||
|
||||
1. If you are distributing a modified version of the libjpeg-turbo source,
|
||||
then:
|
||||
|
||||
1. You cannot alter or remove any existing copyright or license notices
|
||||
from the source.
|
||||
|
||||
**Origin**
|
||||
- Clause 1 of the IJG License
|
||||
- Clause 1 of the Modified BSD License
|
||||
- Clauses 1 and 3 of the zlib License
|
||||
|
||||
2. You must add your own copyright notice to the header of each source
|
||||
file you modified, so others can tell that you modified that file. (If
|
||||
there is not an existing copyright header in that file, then you can
|
||||
simply add a notice stating that you modified the file.)
|
||||
|
||||
**Origin**
|
||||
- Clause 1 of the IJG License
|
||||
- Clause 2 of the zlib License
|
||||
|
||||
3. You must include the IJG README file, and you must not alter any of the
|
||||
copyright or license text in that file.
|
||||
|
||||
**Origin**
|
||||
- Clause 1 of the IJG License
|
||||
|
||||
2. If you are distributing only libjpeg-turbo binaries without the source, or
|
||||
if you are distributing an application that statically links with
|
||||
libjpeg-turbo, then:
|
||||
|
||||
1. Your product documentation must include a message stating:
|
||||
|
||||
This software is based in part on the work of the Independent JPEG
|
||||
Group.
|
||||
|
||||
**Origin**
|
||||
- Clause 2 of the IJG license
|
||||
|
||||
2. If your binary distribution includes or uses the TurboJPEG API, then
|
||||
your product documentation must include the text of the Modified BSD
|
||||
License (see below.)
|
||||
|
||||
**Origin**
|
||||
- Clause 2 of the Modified BSD License
|
||||
|
||||
3. You cannot use the name of the IJG or The libjpeg-turbo Project or the
|
||||
contributors thereof in advertising, publicity, etc.
|
||||
|
||||
**Origin**
|
||||
- IJG License
|
||||
- Clause 3 of the Modified BSD License
|
||||
|
||||
4. The IJG and The libjpeg-turbo Project do not warrant libjpeg-turbo to be
|
||||
free of defects, nor do we accept any liability for undesirable
|
||||
consequences resulting from your use of the software.
|
||||
|
||||
**Origin**
|
||||
- IJG License
|
||||
- Modified BSD License
|
||||
- zlib License
|
||||
|
||||
|
||||
The Modified (3-clause) BSD License
|
||||
===================================
|
||||
|
||||
Copyright (C)2009-2023 D. R. Commander. All Rights Reserved.<br>
|
||||
Copyright (C)2015 Viktor Szathmáry. All Rights Reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
- 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.
|
||||
- Neither the name of the libjpeg-turbo Project 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 HOLDERS 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.
|
||||
|
||||
|
||||
Why Two Licenses?
|
||||
=================
|
||||
|
||||
The zlib License could have been used instead of the Modified (3-clause) BSD
|
||||
License, and since the IJG License effectively subsumes the distribution
|
||||
conditions of the zlib License, this would have effectively placed
|
||||
libjpeg-turbo binary distributions under the IJG License. However, the IJG
|
||||
License specifically refers to the Independent JPEG Group and does not extend
|
||||
attribution and endorsement protections to other entities. Thus, it was
|
||||
desirable to choose a license that granted us the same protections for new code
|
||||
that were granted to the IJG for code derived from their software.
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
libjpeg-turbo note: This file has been modified by The libjpeg-turbo Project
|
||||
to include only information relevant to libjpeg-turbo, to wordsmith certain
|
||||
sections, and to remove impolitic language that existed in the libjpeg v8
|
||||
README. It is included only for reference. Please see README.md for
|
||||
information specific to libjpeg-turbo.
|
||||
|
||||
|
||||
The Independent JPEG Group's JPEG software
|
||||
==========================================
|
||||
|
||||
This distribution contains a release of the Independent JPEG Group's free JPEG
|
||||
software. You are welcome to redistribute this software and to use it for any
|
||||
purpose, subject to the conditions under LEGAL ISSUES, below.
|
||||
|
||||
This software is the work of Tom Lane, Guido Vollbeding, Philip Gladstone,
|
||||
Bill Allombert, Jim Boucher, Lee Crocker, Bob Friesenhahn, Ben Jackson,
|
||||
Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, Ge' Weijers,
|
||||
and other members of the Independent JPEG Group.
|
||||
|
||||
IJG is not affiliated with the ISO/IEC JTC1/SC29/WG1 standards committee
|
||||
(also known as JPEG, together with ITU-T SG16).
|
||||
|
||||
|
||||
DOCUMENTATION ROADMAP
|
||||
=====================
|
||||
|
||||
This file contains the following sections:
|
||||
|
||||
OVERVIEW General description of JPEG and the IJG software.
|
||||
LEGAL ISSUES Copyright, lack of warranty, terms of distribution.
|
||||
REFERENCES Where to learn more about JPEG.
|
||||
ARCHIVE LOCATIONS Where to find newer versions of this software.
|
||||
FILE FORMAT WARS Software *not* to get.
|
||||
TO DO Plans for future IJG releases.
|
||||
|
||||
Other documentation files in the distribution are:
|
||||
|
||||
User documentation:
|
||||
usage.txt Usage instructions for cjpeg, djpeg, jpegtran,
|
||||
rdjpgcom, and wrjpgcom.
|
||||
*.1 Unix-style man pages for programs (same info as usage.txt).
|
||||
wizard.txt Advanced usage instructions for JPEG wizards only.
|
||||
change.log Version-to-version change highlights.
|
||||
Programmer and internal documentation:
|
||||
libjpeg.txt How to use the JPEG library in your own programs.
|
||||
example.c Sample code for calling the JPEG library.
|
||||
structure.txt Overview of the JPEG library's internal structure.
|
||||
coderules.txt Coding style rules --- please read if you contribute code.
|
||||
|
||||
Please read at least usage.txt. Some information can also be found in the JPEG
|
||||
FAQ (Frequently Asked Questions) article. See ARCHIVE LOCATIONS below to find
|
||||
out where to obtain the FAQ article.
|
||||
|
||||
If you want to understand how the JPEG code works, we suggest reading one or
|
||||
more of the REFERENCES, then looking at the documentation files (in roughly
|
||||
the order listed) before diving into the code.
|
||||
|
||||
|
||||
OVERVIEW
|
||||
========
|
||||
|
||||
This package contains C software to implement JPEG image encoding, decoding,
|
||||
and transcoding. JPEG (pronounced "jay-peg") is a standardized compression
|
||||
method for full-color and grayscale images. JPEG's strong suit is compressing
|
||||
photographic images or other types of images that have smooth color and
|
||||
brightness transitions between neighboring pixels. Images with sharp lines or
|
||||
other abrupt features may not compress well with JPEG, and a higher JPEG
|
||||
quality may have to be used to avoid visible compression artifacts with such
|
||||
images.
|
||||
|
||||
JPEG is normally lossy, meaning that the output pixels are not necessarily
|
||||
identical to the input pixels. However, on photographic content and other
|
||||
"smooth" images, very good compression ratios can be obtained with no visible
|
||||
compression artifacts, and extremely high compression ratios are possible if
|
||||
you are willing to sacrifice image quality (by reducing the "quality" setting
|
||||
in the compressor.)
|
||||
|
||||
This software implements JPEG baseline, extended-sequential, progressive, and
|
||||
lossless compression processes. Provision is made for supporting all variants
|
||||
of these processes, although some uncommon parameter settings aren't
|
||||
implemented yet. We have made no provision for supporting the hierarchical
|
||||
processes defined in the standard.
|
||||
|
||||
We provide a set of library routines for reading and writing JPEG image files,
|
||||
plus two sample applications "cjpeg" and "djpeg", which use the library to
|
||||
perform conversion between JPEG and some other popular image file formats.
|
||||
The library is intended to be reused in other applications.
|
||||
|
||||
In order to support file conversion and viewing software, we have included
|
||||
considerable functionality beyond the bare JPEG coding/decoding capability;
|
||||
for example, the color quantization modules are not strictly part of JPEG
|
||||
decoding, but they are essential for output to colormapped file formats. These
|
||||
extra functions can be compiled out of the library if not required for a
|
||||
particular application.
|
||||
|
||||
We have also included "jpegtran", a utility for lossless transcoding between
|
||||
different JPEG processes, and "rdjpgcom" and "wrjpgcom", two simple
|
||||
applications for inserting and extracting textual comments in JFIF files.
|
||||
|
||||
The emphasis in designing this software has been on achieving portability and
|
||||
flexibility, while also making it fast enough to be useful. In particular,
|
||||
the software is not intended to be read as a tutorial on JPEG. (See the
|
||||
REFERENCES section for introductory material.) Rather, it is intended to
|
||||
be reliable, portable, industrial-strength code. We do not claim to have
|
||||
achieved that goal in every aspect of the software, but we strive for it.
|
||||
|
||||
We welcome the use of this software as a component of commercial products.
|
||||
No royalty is required, but we do ask for an acknowledgement in product
|
||||
documentation, as described under LEGAL ISSUES.
|
||||
|
||||
|
||||
LEGAL ISSUES
|
||||
============
|
||||
|
||||
In plain English:
|
||||
|
||||
1. We don't promise that this software works. (But if you find any bugs,
|
||||
please let us know!)
|
||||
2. You can use this software for whatever you want. You don't have to pay us.
|
||||
3. You may not pretend that you wrote this software. If you use it in a
|
||||
program, you must acknowledge somewhere in your documentation that
|
||||
you've used the IJG code.
|
||||
|
||||
In legalese:
|
||||
|
||||
The authors make NO WARRANTY or representation, either express or implied,
|
||||
with respect to this software, its quality, accuracy, merchantability, or
|
||||
fitness for a particular purpose. This software is provided "AS IS", and you,
|
||||
its user, assume the entire risk as to its quality and accuracy.
|
||||
|
||||
This software is copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding.
|
||||
All Rights Reserved except as specified below.
|
||||
|
||||
Permission is hereby granted to use, copy, modify, and distribute this
|
||||
software (or portions thereof) for any purpose, without fee, subject to these
|
||||
conditions:
|
||||
(1) If any part of the source code for this software is distributed, then this
|
||||
README file must be included, with this copyright and no-warranty notice
|
||||
unaltered; and any additions, deletions, or changes to the original files
|
||||
must be clearly indicated in accompanying documentation.
|
||||
(2) If only executable code is distributed, then the accompanying
|
||||
documentation must state that "this software is based in part on the work of
|
||||
the Independent JPEG Group".
|
||||
(3) Permission for use of this software is granted only if the user accepts
|
||||
full responsibility for any undesirable consequences; the authors accept
|
||||
NO LIABILITY for damages of any kind.
|
||||
|
||||
These conditions apply to any software derived from or based on the IJG code,
|
||||
not just to the unmodified library. If you use our work, you ought to
|
||||
acknowledge us.
|
||||
|
||||
Permission is NOT granted for the use of any IJG author's name or company name
|
||||
in advertising or publicity relating to this software or products derived from
|
||||
it. This software may be referred to only as "the Independent JPEG Group's
|
||||
software".
|
||||
|
||||
We specifically permit and encourage the use of this software as the basis of
|
||||
commercial products, provided that all warranty or liability claims are
|
||||
assumed by the product vendor.
|
||||
|
||||
|
||||
REFERENCES
|
||||
==========
|
||||
|
||||
We recommend reading one or more of these references before trying to
|
||||
understand the innards of the JPEG software.
|
||||
|
||||
The best short technical introduction to the JPEG compression algorithm is
|
||||
Wallace, Gregory K. "The JPEG Still Picture Compression Standard",
|
||||
Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44.
|
||||
(Adjacent articles in that issue discuss MPEG motion picture compression,
|
||||
applications of JPEG, and related topics.) If you don't have the CACM issue
|
||||
handy, a PDF file containing a revised version of Wallace's article is
|
||||
available at http://www.ijg.org/files/Wallace.JPEG.pdf. The file (actually
|
||||
a preprint for an article that appeared in IEEE Trans. Consumer Electronics)
|
||||
omits the sample images that appeared in CACM, but it includes corrections
|
||||
and some added material. Note: the Wallace article is copyright ACM and IEEE,
|
||||
and it may not be used for commercial purposes.
|
||||
|
||||
A somewhat less technical, more leisurely introduction to JPEG can be found in
|
||||
"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by
|
||||
M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides
|
||||
good explanations and example C code for a multitude of compression methods
|
||||
including JPEG. It is an excellent source if you are comfortable reading C
|
||||
code but don't know much about data compression in general. The book's JPEG
|
||||
sample code is far from industrial-strength, but when you are ready to look
|
||||
at a full implementation, you've got one here...
|
||||
|
||||
The best currently available description of JPEG is the textbook "JPEG Still
|
||||
Image Data Compression Standard" by William B. Pennebaker and Joan L.
|
||||
Mitchell, published by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1.
|
||||
Price US$59.95, 638 pp. The book includes the complete text of the ISO JPEG
|
||||
standards (DIS 10918-1 and draft DIS 10918-2).
|
||||
|
||||
The original JPEG standard is divided into two parts, Part 1 being the actual
|
||||
specification, while Part 2 covers compliance testing methods. Part 1 is
|
||||
titled "Digital Compression and Coding of Continuous-tone Still Images,
|
||||
Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS
|
||||
10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of
|
||||
Continuous-tone Still Images, Part 2: Compliance testing" and has document
|
||||
numbers ISO/IEC IS 10918-2, ITU-T T.83.
|
||||
|
||||
The JPEG standard does not specify all details of an interchangeable file
|
||||
format. For the omitted details, we follow the "JFIF" conventions, revision
|
||||
1.02. JFIF version 1 has been adopted as ISO/IEC 10918-5 (05/2013) and
|
||||
Recommendation ITU-T T.871 (05/2011): Information technology - Digital
|
||||
compression and coding of continuous-tone still images: JPEG File Interchange
|
||||
Format (JFIF). It is available as a free download in PDF file format from
|
||||
https://www.iso.org/standard/54989.html and http://www.itu.int/rec/T-REC-T.871.
|
||||
A PDF file of the older JFIF 1.02 specification is available at
|
||||
http://www.w3.org/Graphics/JPEG/jfif3.pdf.
|
||||
|
||||
The TIFF 6.0 file format specification can be obtained from
|
||||
http://mirrors.ctan.org/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation
|
||||
scheme found in the TIFF 6.0 spec of 3-June-92 has a number of serious
|
||||
problems. IJG does not recommend use of the TIFF 6.0 design (TIFF Compression
|
||||
tag 6). Instead, we recommend the JPEG design proposed by TIFF Technical Note
|
||||
#2 (Compression tag 7). Copies of this Note can be obtained from
|
||||
http://www.ijg.org/files/. It is expected that the next revision
|
||||
of the TIFF spec will replace the 6.0 JPEG design with the Note's design.
|
||||
Although IJG's own code does not support TIFF/JPEG, the free libtiff library
|
||||
uses our library to implement TIFF/JPEG per the Note.
|
||||
|
||||
|
||||
ARCHIVE LOCATIONS
|
||||
=================
|
||||
|
||||
The "official" archive site for this software is www.ijg.org.
|
||||
The most recent released version can always be found there in
|
||||
directory "files".
|
||||
|
||||
The JPEG FAQ (Frequently Asked Questions) article is a source of some
|
||||
general information about JPEG. It is available at
|
||||
http://www.faqs.org/faqs/jpeg-faq.
|
||||
|
||||
|
||||
FILE FORMAT COMPATIBILITY
|
||||
=========================
|
||||
|
||||
This software implements ITU T.81 | ISO/IEC 10918 with some extensions from
|
||||
ITU T.871 | ISO/IEC 10918-5 (JPEG File Interchange Format-- see REFERENCES).
|
||||
Informally, the term "JPEG image" or "JPEG file" most often refers to JFIF or
|
||||
a subset thereof, but there are other formats containing the name "JPEG" that
|
||||
are incompatible with the original JPEG standard or with JFIF (for instance,
|
||||
JPEG 2000 and JPEG XR). This software therefore does not support these
|
||||
formats. Indeed, one of the original reasons for developing this free software
|
||||
was to help force convergence on a common, interoperable format standard for
|
||||
JPEG files.
|
||||
|
||||
JFIF is a minimal or "low end" representation. TIFF/JPEG (TIFF revision 6.0 as
|
||||
modified by TIFF Technical Note #2) can be used for "high end" applications
|
||||
that need to record a lot of additional data about an image.
|
||||
|
||||
|
||||
TO DO
|
||||
=====
|
||||
|
||||
Please send bug reports, offers of help, etc. to jpeg-info@jpegclub.org.
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* jaricom.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Developed 1997-2009 by Guido Vollbeding.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2015, 2018, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains probability estimation tables for common use in
|
||||
* arithmetic entropy encoding and decoding routines.
|
||||
*
|
||||
* This data represents Table D.2 in
|
||||
* Recommendation ITU-T T.81 (1992) | ISO/IEC 10918-1:1994 and Table 24 in
|
||||
* Recommendation ITU-T T.82 (1993) | ISO/IEC 11544:1993.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
|
||||
/* The following #define specifies the packing of the four components
|
||||
* into the compact JLONG representation.
|
||||
* Note that this formula must match the actual arithmetic encoder
|
||||
* and decoder implementation. The implementation has to be changed
|
||||
* if this formula is changed.
|
||||
* The current organization is leaned on Markus Kuhn's JBIG
|
||||
* implementation (jbig_tab.c).
|
||||
*/
|
||||
|
||||
#define V(i, a, b, c, d) \
|
||||
(((JLONG)a << 16) | ((JLONG)c << 8) | ((JLONG)d << 7) | b)
|
||||
|
||||
const JLONG jpeg_aritab[113 + 1] = {
|
||||
/*
|
||||
* Index, Qe_Value, Next_Index_LPS, Next_Index_MPS, Switch_MPS
|
||||
*/
|
||||
V( 0, 0x5a1d, 1, 1, 1 ),
|
||||
V( 1, 0x2586, 14, 2, 0 ),
|
||||
V( 2, 0x1114, 16, 3, 0 ),
|
||||
V( 3, 0x080b, 18, 4, 0 ),
|
||||
V( 4, 0x03d8, 20, 5, 0 ),
|
||||
V( 5, 0x01da, 23, 6, 0 ),
|
||||
V( 6, 0x00e5, 25, 7, 0 ),
|
||||
V( 7, 0x006f, 28, 8, 0 ),
|
||||
V( 8, 0x0036, 30, 9, 0 ),
|
||||
V( 9, 0x001a, 33, 10, 0 ),
|
||||
V( 10, 0x000d, 35, 11, 0 ),
|
||||
V( 11, 0x0006, 9, 12, 0 ),
|
||||
V( 12, 0x0003, 10, 13, 0 ),
|
||||
V( 13, 0x0001, 12, 13, 0 ),
|
||||
V( 14, 0x5a7f, 15, 15, 1 ),
|
||||
V( 15, 0x3f25, 36, 16, 0 ),
|
||||
V( 16, 0x2cf2, 38, 17, 0 ),
|
||||
V( 17, 0x207c, 39, 18, 0 ),
|
||||
V( 18, 0x17b9, 40, 19, 0 ),
|
||||
V( 19, 0x1182, 42, 20, 0 ),
|
||||
V( 20, 0x0cef, 43, 21, 0 ),
|
||||
V( 21, 0x09a1, 45, 22, 0 ),
|
||||
V( 22, 0x072f, 46, 23, 0 ),
|
||||
V( 23, 0x055c, 48, 24, 0 ),
|
||||
V( 24, 0x0406, 49, 25, 0 ),
|
||||
V( 25, 0x0303, 51, 26, 0 ),
|
||||
V( 26, 0x0240, 52, 27, 0 ),
|
||||
V( 27, 0x01b1, 54, 28, 0 ),
|
||||
V( 28, 0x0144, 56, 29, 0 ),
|
||||
V( 29, 0x00f5, 57, 30, 0 ),
|
||||
V( 30, 0x00b7, 59, 31, 0 ),
|
||||
V( 31, 0x008a, 60, 32, 0 ),
|
||||
V( 32, 0x0068, 62, 33, 0 ),
|
||||
V( 33, 0x004e, 63, 34, 0 ),
|
||||
V( 34, 0x003b, 32, 35, 0 ),
|
||||
V( 35, 0x002c, 33, 9, 0 ),
|
||||
V( 36, 0x5ae1, 37, 37, 1 ),
|
||||
V( 37, 0x484c, 64, 38, 0 ),
|
||||
V( 38, 0x3a0d, 65, 39, 0 ),
|
||||
V( 39, 0x2ef1, 67, 40, 0 ),
|
||||
V( 40, 0x261f, 68, 41, 0 ),
|
||||
V( 41, 0x1f33, 69, 42, 0 ),
|
||||
V( 42, 0x19a8, 70, 43, 0 ),
|
||||
V( 43, 0x1518, 72, 44, 0 ),
|
||||
V( 44, 0x1177, 73, 45, 0 ),
|
||||
V( 45, 0x0e74, 74, 46, 0 ),
|
||||
V( 46, 0x0bfb, 75, 47, 0 ),
|
||||
V( 47, 0x09f8, 77, 48, 0 ),
|
||||
V( 48, 0x0861, 78, 49, 0 ),
|
||||
V( 49, 0x0706, 79, 50, 0 ),
|
||||
V( 50, 0x05cd, 48, 51, 0 ),
|
||||
V( 51, 0x04de, 50, 52, 0 ),
|
||||
V( 52, 0x040f, 50, 53, 0 ),
|
||||
V( 53, 0x0363, 51, 54, 0 ),
|
||||
V( 54, 0x02d4, 52, 55, 0 ),
|
||||
V( 55, 0x025c, 53, 56, 0 ),
|
||||
V( 56, 0x01f8, 54, 57, 0 ),
|
||||
V( 57, 0x01a4, 55, 58, 0 ),
|
||||
V( 58, 0x0160, 56, 59, 0 ),
|
||||
V( 59, 0x0125, 57, 60, 0 ),
|
||||
V( 60, 0x00f6, 58, 61, 0 ),
|
||||
V( 61, 0x00cb, 59, 62, 0 ),
|
||||
V( 62, 0x00ab, 61, 63, 0 ),
|
||||
V( 63, 0x008f, 61, 32, 0 ),
|
||||
V( 64, 0x5b12, 65, 65, 1 ),
|
||||
V( 65, 0x4d04, 80, 66, 0 ),
|
||||
V( 66, 0x412c, 81, 67, 0 ),
|
||||
V( 67, 0x37d8, 82, 68, 0 ),
|
||||
V( 68, 0x2fe8, 83, 69, 0 ),
|
||||
V( 69, 0x293c, 84, 70, 0 ),
|
||||
V( 70, 0x2379, 86, 71, 0 ),
|
||||
V( 71, 0x1edf, 87, 72, 0 ),
|
||||
V( 72, 0x1aa9, 87, 73, 0 ),
|
||||
V( 73, 0x174e, 72, 74, 0 ),
|
||||
V( 74, 0x1424, 72, 75, 0 ),
|
||||
V( 75, 0x119c, 74, 76, 0 ),
|
||||
V( 76, 0x0f6b, 74, 77, 0 ),
|
||||
V( 77, 0x0d51, 75, 78, 0 ),
|
||||
V( 78, 0x0bb6, 77, 79, 0 ),
|
||||
V( 79, 0x0a40, 77, 48, 0 ),
|
||||
V( 80, 0x5832, 80, 81, 1 ),
|
||||
V( 81, 0x4d1c, 88, 82, 0 ),
|
||||
V( 82, 0x438e, 89, 83, 0 ),
|
||||
V( 83, 0x3bdd, 90, 84, 0 ),
|
||||
V( 84, 0x34ee, 91, 85, 0 ),
|
||||
V( 85, 0x2eae, 92, 86, 0 ),
|
||||
V( 86, 0x299a, 93, 87, 0 ),
|
||||
V( 87, 0x2516, 86, 71, 0 ),
|
||||
V( 88, 0x5570, 88, 89, 1 ),
|
||||
V( 89, 0x4ca9, 95, 90, 0 ),
|
||||
V( 90, 0x44d9, 96, 91, 0 ),
|
||||
V( 91, 0x3e22, 97, 92, 0 ),
|
||||
V( 92, 0x3824, 99, 93, 0 ),
|
||||
V( 93, 0x32b4, 99, 94, 0 ),
|
||||
V( 94, 0x2e17, 93, 86, 0 ),
|
||||
V( 95, 0x56a8, 95, 96, 1 ),
|
||||
V( 96, 0x4f46, 101, 97, 0 ),
|
||||
V( 97, 0x47e5, 102, 98, 0 ),
|
||||
V( 98, 0x41cf, 103, 99, 0 ),
|
||||
V( 99, 0x3c3d, 104, 100, 0 ),
|
||||
V( 100, 0x375e, 99, 93, 0 ),
|
||||
V( 101, 0x5231, 105, 102, 0 ),
|
||||
V( 102, 0x4c0f, 106, 103, 0 ),
|
||||
V( 103, 0x4639, 107, 104, 0 ),
|
||||
V( 104, 0x415e, 103, 99, 0 ),
|
||||
V( 105, 0x5627, 105, 106, 1 ),
|
||||
V( 106, 0x50e7, 108, 107, 0 ),
|
||||
V( 107, 0x4b85, 109, 103, 0 ),
|
||||
V( 108, 0x5597, 110, 109, 0 ),
|
||||
V( 109, 0x504f, 111, 107, 0 ),
|
||||
V( 110, 0x5a10, 110, 111, 1 ),
|
||||
V( 111, 0x5522, 112, 109, 0 ),
|
||||
V( 112, 0x59eb, 112, 111, 1 ),
|
||||
/*
|
||||
* This last entry is used for fixed probability estimate of 0.5
|
||||
* as recommended in Section 10.3 Table 5 of ITU-T Rec. T.851.
|
||||
*/
|
||||
V( 113, 0x5a1d, 113, 113, 0 )
|
||||
};
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* jcapimin.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1994-1998, Thomas G. Lane.
|
||||
* Modified 2003-2010 by Guido Vollbeding.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains application interface code for the compression half
|
||||
* of the JPEG library. These are the "minimum" API routines that may be
|
||||
* needed in either the normal full-compression case or the transcoding-only
|
||||
* case.
|
||||
*
|
||||
* Most of the routines intended to be called directly by an application
|
||||
* are in this file or in jcapistd.c. But also see jcparam.c for
|
||||
* parameter-setup helper routines, jcomapi.c for routines shared by
|
||||
* compression and decompression, and jctrans.c for the transcoding case.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jcmaster.h"
|
||||
|
||||
|
||||
/*
|
||||
* Initialization of a JPEG compression object.
|
||||
* The error manager must already be set up (in case memory manager fails).
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_CreateCompress(j_compress_ptr cinfo, int version, size_t structsize)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Guard against version mismatches between library and caller. */
|
||||
cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */
|
||||
if (version != JPEG_LIB_VERSION)
|
||||
ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version);
|
||||
if (structsize != sizeof(struct jpeg_compress_struct))
|
||||
ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE,
|
||||
(int)sizeof(struct jpeg_compress_struct), (int)structsize);
|
||||
|
||||
/* For debugging purposes, we zero the whole master structure.
|
||||
* But the application has already set the err pointer, and may have set
|
||||
* client_data, so we have to save and restore those fields.
|
||||
* Note: if application hasn't set client_data, tools like Purify may
|
||||
* complain here.
|
||||
*/
|
||||
{
|
||||
struct jpeg_error_mgr *err = cinfo->err;
|
||||
void *client_data = cinfo->client_data; /* ignore Purify complaint here */
|
||||
memset(cinfo, 0, sizeof(struct jpeg_compress_struct));
|
||||
cinfo->err = err;
|
||||
cinfo->client_data = client_data;
|
||||
}
|
||||
cinfo->is_decompressor = FALSE;
|
||||
|
||||
/* Initialize a memory manager instance for this object */
|
||||
jinit_memory_mgr((j_common_ptr)cinfo);
|
||||
|
||||
/* Zero out pointers to permanent structures. */
|
||||
cinfo->progress = NULL;
|
||||
cinfo->dest = NULL;
|
||||
|
||||
cinfo->comp_info = NULL;
|
||||
|
||||
for (i = 0; i < NUM_QUANT_TBLS; i++) {
|
||||
cinfo->quant_tbl_ptrs[i] = NULL;
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
cinfo->q_scale_factor[i] = 100;
|
||||
#endif
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_HUFF_TBLS; i++) {
|
||||
cinfo->dc_huff_tbl_ptrs[i] = NULL;
|
||||
cinfo->ac_huff_tbl_ptrs[i] = NULL;
|
||||
}
|
||||
|
||||
#if JPEG_LIB_VERSION >= 80
|
||||
/* Must do it here for emit_dqt in case jpeg_write_tables is used */
|
||||
cinfo->block_size = DCTSIZE;
|
||||
cinfo->natural_order = jpeg_natural_order;
|
||||
cinfo->lim_Se = DCTSIZE2 - 1;
|
||||
#endif
|
||||
|
||||
cinfo->script_space = NULL;
|
||||
|
||||
cinfo->input_gamma = 1.0; /* in case application forgets */
|
||||
|
||||
cinfo->data_precision = BITS_IN_JSAMPLE;
|
||||
|
||||
/* OK, I'm ready */
|
||||
cinfo->global_state = CSTATE_START;
|
||||
|
||||
/* The master struct is used to store extension parameters, so we allocate it
|
||||
* here.
|
||||
*/
|
||||
cinfo->master = (struct jpeg_comp_master *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
|
||||
sizeof(my_comp_master));
|
||||
memset(cinfo->master, 0, sizeof(my_comp_master));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destruction of a JPEG compression object
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_destroy_compress(j_compress_ptr cinfo)
|
||||
{
|
||||
jpeg_destroy((j_common_ptr)cinfo); /* use common routine */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Abort processing of a JPEG compression operation,
|
||||
* but don't destroy the object itself.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_abort_compress(j_compress_ptr cinfo)
|
||||
{
|
||||
jpeg_abort((j_common_ptr)cinfo); /* use common routine */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Forcibly suppress or un-suppress all quantization and Huffman tables.
|
||||
* Marks all currently defined tables as already written (if suppress)
|
||||
* or not written (if !suppress). This will control whether they get emitted
|
||||
* by a subsequent jpeg_start_compress call.
|
||||
*
|
||||
* This routine is exported for use by applications that want to produce
|
||||
* abbreviated JPEG datastreams. It logically belongs in jcparam.c, but
|
||||
* since it is called by jpeg_start_compress, we put it here --- otherwise
|
||||
* jcparam.o would be linked whether the application used it or not.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_suppress_tables(j_compress_ptr cinfo, boolean suppress)
|
||||
{
|
||||
int i;
|
||||
JQUANT_TBL *qtbl;
|
||||
JHUFF_TBL *htbl;
|
||||
|
||||
for (i = 0; i < NUM_QUANT_TBLS; i++) {
|
||||
if ((qtbl = cinfo->quant_tbl_ptrs[i]) != NULL)
|
||||
qtbl->sent_table = suppress;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_HUFF_TBLS; i++) {
|
||||
if ((htbl = cinfo->dc_huff_tbl_ptrs[i]) != NULL)
|
||||
htbl->sent_table = suppress;
|
||||
if ((htbl = cinfo->ac_huff_tbl_ptrs[i]) != NULL)
|
||||
htbl->sent_table = suppress;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Finish JPEG compression.
|
||||
*
|
||||
* If a multipass operating mode was selected, this may do a great deal of
|
||||
* work including most of the actual output.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_finish_compress(j_compress_ptr cinfo)
|
||||
{
|
||||
JDIMENSION iMCU_row;
|
||||
|
||||
if (cinfo->global_state == CSTATE_SCANNING ||
|
||||
cinfo->global_state == CSTATE_RAW_OK) {
|
||||
/* Terminate first pass */
|
||||
if (cinfo->next_scanline < cinfo->image_height)
|
||||
ERREXIT(cinfo, JERR_TOO_LITTLE_DATA);
|
||||
(*cinfo->master->finish_pass) (cinfo);
|
||||
} else if (cinfo->global_state != CSTATE_WRCOEFS)
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
/* Perform any remaining passes */
|
||||
while (!cinfo->master->is_last_pass) {
|
||||
(*cinfo->master->prepare_for_pass) (cinfo);
|
||||
for (iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++) {
|
||||
if (cinfo->progress != NULL) {
|
||||
cinfo->progress->pass_counter = (long)iMCU_row;
|
||||
cinfo->progress->pass_limit = (long)cinfo->total_iMCU_rows;
|
||||
(*cinfo->progress->progress_monitor) ((j_common_ptr)cinfo);
|
||||
}
|
||||
/* We bypass the main controller and invoke coef controller directly;
|
||||
* all work is being done from the coefficient buffer.
|
||||
*/
|
||||
if (cinfo->data_precision == 16) {
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
if (!(*cinfo->coef->compress_data_16) (cinfo, (J16SAMPIMAGE)NULL))
|
||||
ERREXIT(cinfo, JERR_CANT_SUSPEND);
|
||||
#else
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
#endif
|
||||
} else if (cinfo->data_precision == 12) {
|
||||
if (!(*cinfo->coef->compress_data_12) (cinfo, (J12SAMPIMAGE)NULL))
|
||||
ERREXIT(cinfo, JERR_CANT_SUSPEND);
|
||||
} else {
|
||||
if (!(*cinfo->coef->compress_data) (cinfo, (JSAMPIMAGE)NULL))
|
||||
ERREXIT(cinfo, JERR_CANT_SUSPEND);
|
||||
}
|
||||
}
|
||||
(*cinfo->master->finish_pass) (cinfo);
|
||||
}
|
||||
/* Write EOI, do final cleanup */
|
||||
(*cinfo->marker->write_file_trailer) (cinfo);
|
||||
(*cinfo->dest->term_destination) (cinfo);
|
||||
/* We can use jpeg_abort to release memory and reset global_state */
|
||||
jpeg_abort((j_common_ptr)cinfo);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write a special marker.
|
||||
* This is only recommended for writing COM or APPn markers.
|
||||
* Must be called after jpeg_start_compress() and before
|
||||
* first call to jpeg_write_scanlines() or jpeg_write_raw_data().
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_write_marker(j_compress_ptr cinfo, int marker, const JOCTET *dataptr,
|
||||
unsigned int datalen)
|
||||
{
|
||||
void (*write_marker_byte) (j_compress_ptr info, int val);
|
||||
|
||||
if (cinfo->next_scanline != 0 ||
|
||||
(cinfo->global_state != CSTATE_SCANNING &&
|
||||
cinfo->global_state != CSTATE_RAW_OK &&
|
||||
cinfo->global_state != CSTATE_WRCOEFS))
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
|
||||
(*cinfo->marker->write_marker_header) (cinfo, marker, datalen);
|
||||
write_marker_byte = cinfo->marker->write_marker_byte; /* copy for speed */
|
||||
while (datalen--) {
|
||||
(*write_marker_byte) (cinfo, *dataptr);
|
||||
dataptr++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Same, but piecemeal. */
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_write_m_header(j_compress_ptr cinfo, int marker, unsigned int datalen)
|
||||
{
|
||||
if (cinfo->next_scanline != 0 ||
|
||||
(cinfo->global_state != CSTATE_SCANNING &&
|
||||
cinfo->global_state != CSTATE_RAW_OK &&
|
||||
cinfo->global_state != CSTATE_WRCOEFS))
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
|
||||
(*cinfo->marker->write_marker_header) (cinfo, marker, datalen);
|
||||
}
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_write_m_byte(j_compress_ptr cinfo, int val)
|
||||
{
|
||||
(*cinfo->marker->write_marker_byte) (cinfo, val);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Alternate compression function: just write an abbreviated table file.
|
||||
* Before calling this, all parameters and a data destination must be set up.
|
||||
*
|
||||
* To produce a pair of files containing abbreviated tables and abbreviated
|
||||
* image data, one would proceed as follows:
|
||||
*
|
||||
* initialize JPEG object
|
||||
* set JPEG parameters
|
||||
* set destination to table file
|
||||
* jpeg_write_tables(cinfo);
|
||||
* set destination to image file
|
||||
* jpeg_start_compress(cinfo, FALSE);
|
||||
* write data...
|
||||
* jpeg_finish_compress(cinfo);
|
||||
*
|
||||
* jpeg_write_tables has the side effect of marking all tables written
|
||||
* (same as jpeg_suppress_tables(..., TRUE)). Thus a subsequent start_compress
|
||||
* will not re-emit the tables unless it is passed write_all_tables=TRUE.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_write_tables(j_compress_ptr cinfo)
|
||||
{
|
||||
if (cinfo->global_state != CSTATE_START)
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
|
||||
/* (Re)initialize error mgr and destination modules */
|
||||
(*cinfo->err->reset_error_mgr) ((j_common_ptr)cinfo);
|
||||
(*cinfo->dest->init_destination) (cinfo);
|
||||
/* Initialize the marker writer ... bit of a crock to do it here. */
|
||||
jinit_marker_writer(cinfo);
|
||||
/* Write them tables! */
|
||||
(*cinfo->marker->write_tables_only) (cinfo);
|
||||
/* And clean up. */
|
||||
(*cinfo->dest->term_destination) (cinfo);
|
||||
/*
|
||||
* In library releases up through v6a, we called jpeg_abort() here to free
|
||||
* any working memory allocated by the destination manager and marker
|
||||
* writer. Some applications had a problem with that: they allocated space
|
||||
* of their own from the library memory manager, and didn't want it to go
|
||||
* away during write_tables. So now we do nothing. This will cause a
|
||||
* memory leak if an app calls write_tables repeatedly without doing a full
|
||||
* compression cycle or otherwise resetting the JPEG object. However, that
|
||||
* seems less bad than unexpectedly freeing memory in the normal case.
|
||||
* An app that prefers the old behavior can call jpeg_abort for itself after
|
||||
* each call to jpeg_write_tables().
|
||||
*/
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* jcapistd.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1994-1996, Thomas G. Lane.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains application interface code for the compression half
|
||||
* of the JPEG library. These are the "standard" API routines that are
|
||||
* used in the normal full-compression case. They are not used by a
|
||||
* transcoding-only application. Note that if an application links in
|
||||
* jpeg_start_compress, it will end up linking in the entire compressor.
|
||||
* We thus must separate this file from jcapimin.c to avoid linking the
|
||||
* whole compression library into a transcoder.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jsamplecomp.h"
|
||||
|
||||
|
||||
#if BITS_IN_JSAMPLE == 8
|
||||
|
||||
/*
|
||||
* Compression initialization.
|
||||
* Before calling this, all parameters and a data destination must be set up.
|
||||
*
|
||||
* We require a write_all_tables parameter as a failsafe check when writing
|
||||
* multiple datastreams from the same compression object. Since prior runs
|
||||
* will have left all the tables marked sent_table=TRUE, a subsequent run
|
||||
* would emit an abbreviated stream (no tables) by default. This may be what
|
||||
* is wanted, but for safety's sake it should not be the default behavior:
|
||||
* programmers should have to make a deliberate choice to emit abbreviated
|
||||
* images. Therefore the documentation and examples should encourage people
|
||||
* to pass write_all_tables=TRUE; then it will take active thought to do the
|
||||
* wrong thing.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_start_compress(j_compress_ptr cinfo, boolean write_all_tables)
|
||||
{
|
||||
if (cinfo->global_state != CSTATE_START)
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
|
||||
if (write_all_tables)
|
||||
jpeg_suppress_tables(cinfo, FALSE); /* mark all tables to be written */
|
||||
|
||||
/* (Re)initialize error mgr and destination modules */
|
||||
(*cinfo->err->reset_error_mgr) ((j_common_ptr)cinfo);
|
||||
(*cinfo->dest->init_destination) (cinfo);
|
||||
/* Perform master selection of active modules */
|
||||
jinit_compress_master(cinfo);
|
||||
/* Set up for the first pass */
|
||||
(*cinfo->master->prepare_for_pass) (cinfo);
|
||||
/* Ready for application to drive first pass through _jpeg_write_scanlines
|
||||
* or _jpeg_write_raw_data.
|
||||
*/
|
||||
cinfo->next_scanline = 0;
|
||||
cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Write some scanlines of data to the JPEG compressor.
|
||||
*
|
||||
* The return value will be the number of lines actually written.
|
||||
* This should be less than the supplied num_lines only in case that
|
||||
* the data destination module has requested suspension of the compressor,
|
||||
* or if more than image_height scanlines are passed in.
|
||||
*
|
||||
* Note: we warn about excess calls to _jpeg_write_scanlines() since
|
||||
* this likely signals an application programmer error. However,
|
||||
* excess scanlines passed in the last valid call are *silently* ignored,
|
||||
* so that the application need not adjust num_lines for end-of-image
|
||||
* when using a multiple-scanline buffer.
|
||||
*/
|
||||
|
||||
GLOBAL(JDIMENSION)
|
||||
_jpeg_write_scanlines(j_compress_ptr cinfo, _JSAMPARRAY scanlines,
|
||||
JDIMENSION num_lines)
|
||||
{
|
||||
#if BITS_IN_JSAMPLE != 16 || defined(C_LOSSLESS_SUPPORTED)
|
||||
JDIMENSION row_ctr, rows_left;
|
||||
|
||||
if (cinfo->data_precision != BITS_IN_JSAMPLE)
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
|
||||
if (cinfo->global_state != CSTATE_SCANNING)
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
if (cinfo->next_scanline >= cinfo->image_height)
|
||||
WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
|
||||
|
||||
/* Call progress monitor hook if present */
|
||||
if (cinfo->progress != NULL) {
|
||||
cinfo->progress->pass_counter = (long)cinfo->next_scanline;
|
||||
cinfo->progress->pass_limit = (long)cinfo->image_height;
|
||||
(*cinfo->progress->progress_monitor) ((j_common_ptr)cinfo);
|
||||
}
|
||||
|
||||
/* Give master control module another chance if this is first call to
|
||||
* _jpeg_write_scanlines. This lets output of the frame/scan headers be
|
||||
* delayed so that application can write COM, etc, markers between
|
||||
* jpeg_start_compress and _jpeg_write_scanlines.
|
||||
*/
|
||||
if (cinfo->master->call_pass_startup)
|
||||
(*cinfo->master->pass_startup) (cinfo);
|
||||
|
||||
/* Ignore any extra scanlines at bottom of image. */
|
||||
rows_left = cinfo->image_height - cinfo->next_scanline;
|
||||
if (num_lines > rows_left)
|
||||
num_lines = rows_left;
|
||||
|
||||
row_ctr = 0;
|
||||
(*cinfo->main->_process_data) (cinfo, scanlines, &row_ctr, num_lines);
|
||||
cinfo->next_scanline += row_ctr;
|
||||
return row_ctr;
|
||||
#else
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if BITS_IN_JSAMPLE != 16
|
||||
|
||||
/*
|
||||
* Alternate entry point to write raw data.
|
||||
* Processes exactly one iMCU row per call, unless suspended.
|
||||
*/
|
||||
|
||||
GLOBAL(JDIMENSION)
|
||||
_jpeg_write_raw_data(j_compress_ptr cinfo, _JSAMPIMAGE data,
|
||||
JDIMENSION num_lines)
|
||||
{
|
||||
JDIMENSION lines_per_iMCU_row;
|
||||
|
||||
if (cinfo->data_precision != BITS_IN_JSAMPLE)
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
|
||||
if (cinfo->master->lossless)
|
||||
ERREXIT(cinfo, JERR_NOTIMPL);
|
||||
|
||||
if (cinfo->global_state != CSTATE_RAW_OK)
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
if (cinfo->next_scanline >= cinfo->image_height) {
|
||||
WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Call progress monitor hook if present */
|
||||
if (cinfo->progress != NULL) {
|
||||
cinfo->progress->pass_counter = (long)cinfo->next_scanline;
|
||||
cinfo->progress->pass_limit = (long)cinfo->image_height;
|
||||
(*cinfo->progress->progress_monitor) ((j_common_ptr)cinfo);
|
||||
}
|
||||
|
||||
/* Give master control module another chance if this is first call to
|
||||
* _jpeg_write_raw_data. This lets output of the frame/scan headers be
|
||||
* delayed so that application can write COM, etc, markers between
|
||||
* jpeg_start_compress and _jpeg_write_raw_data.
|
||||
*/
|
||||
if (cinfo->master->call_pass_startup)
|
||||
(*cinfo->master->pass_startup) (cinfo);
|
||||
|
||||
/* Verify that at least one iMCU row has been passed. */
|
||||
lines_per_iMCU_row = cinfo->max_v_samp_factor * DCTSIZE;
|
||||
if (num_lines < lines_per_iMCU_row)
|
||||
ERREXIT(cinfo, JERR_BUFFER_SIZE);
|
||||
|
||||
/* Directly compress the row. */
|
||||
if (!(*cinfo->coef->_compress_data) (cinfo, data)) {
|
||||
/* If compressor did not consume the whole row, suspend processing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* OK, we processed one iMCU row. */
|
||||
cinfo->next_scanline += lines_per_iMCU_row;
|
||||
return lines_per_iMCU_row;
|
||||
}
|
||||
|
||||
#endif /* BITS_IN_JSAMPLE != 16 */
|
||||
+932
@@ -0,0 +1,932 @@
|
||||
/*
|
||||
* jcarith.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Developed 1997-2009 by Guido Vollbeding.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2015, 2018, 2021-2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains portable arithmetic entropy encoding routines for JPEG
|
||||
* (implementing Recommendation ITU-T T.81 | ISO/IEC 10918-1).
|
||||
*
|
||||
* Both sequential and progressive modes are supported in this single module.
|
||||
*
|
||||
* Suspension is not currently supported in this module.
|
||||
*
|
||||
* NOTE: All referenced figures are from
|
||||
* Recommendation ITU-T T.81 (1992) | ISO/IEC 10918-1:1994.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
|
||||
|
||||
/* Expanded entropy encoder object for arithmetic encoding. */
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_entropy_encoder pub; /* public fields */
|
||||
|
||||
JLONG c; /* C register, base of coding interval, layout as in sec. D.1.3 */
|
||||
JLONG a; /* A register, normalized size of coding interval */
|
||||
JLONG sc; /* counter for stacked 0xFF values which might overflow */
|
||||
JLONG zc; /* counter for pending 0x00 output values which might *
|
||||
* be discarded at the end ("Pacman" termination) */
|
||||
int ct; /* bit shift counter, determines when next byte will be written */
|
||||
int buffer; /* buffer for most recent output byte != 0xFF */
|
||||
|
||||
int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */
|
||||
int dc_context[MAX_COMPS_IN_SCAN]; /* context index for DC conditioning */
|
||||
|
||||
unsigned int restarts_to_go; /* MCUs left in this restart interval */
|
||||
int next_restart_num; /* next restart number to write (0-7) */
|
||||
|
||||
/* Pointers to statistics areas (these workspaces have image lifespan) */
|
||||
unsigned char *dc_stats[NUM_ARITH_TBLS];
|
||||
unsigned char *ac_stats[NUM_ARITH_TBLS];
|
||||
|
||||
/* Statistics bin for coding with fixed probability 0.5 */
|
||||
unsigned char fixed_bin[4];
|
||||
} arith_entropy_encoder;
|
||||
|
||||
typedef arith_entropy_encoder *arith_entropy_ptr;
|
||||
|
||||
/* The following two definitions specify the allocation chunk size
|
||||
* for the statistics area.
|
||||
* According to sections F.1.4.4.1.3 and F.1.4.4.2, we need at least
|
||||
* 49 statistics bins for DC, and 245 statistics bins for AC coding.
|
||||
*
|
||||
* We use a compact representation with 1 byte per statistics bin,
|
||||
* thus the numbers directly represent byte sizes.
|
||||
* This 1 byte per statistics bin contains the meaning of the MPS
|
||||
* (more probable symbol) in the highest bit (mask 0x80), and the
|
||||
* index into the probability estimation state machine table
|
||||
* in the lower bits (mask 0x7F).
|
||||
*/
|
||||
|
||||
#define DC_STAT_BINS 64
|
||||
#define AC_STAT_BINS 256
|
||||
|
||||
/* NOTE: Uncomment the following #define if you want to use the
|
||||
* given formula for calculating the AC conditioning parameter Kx
|
||||
* for spectral selection progressive coding in section G.1.3.2
|
||||
* of the spec (Kx = Kmin + SRL (8 + Se - Kmin) 4).
|
||||
* Although the spec and P&M authors claim that this "has proven
|
||||
* to give good results for 8 bit precision samples", I'm not
|
||||
* convinced yet that this is really beneficial.
|
||||
* Early tests gave only very marginal compression enhancements
|
||||
* (a few - around 5 or so - bytes even for very large files),
|
||||
* which would turn out rather negative if we'd suppress the
|
||||
* DAC (Define Arithmetic Conditioning) marker segments for
|
||||
* the default parameters in the future.
|
||||
* Note that currently the marker writing module emits 12-byte
|
||||
* DAC segments for a full-component scan in a color image.
|
||||
* This is not worth worrying about IMHO. However, since the
|
||||
* spec defines the default values to be used if the tables
|
||||
* are omitted (unlike Huffman tables, which are required
|
||||
* anyway), one might optimize this behaviour in the future,
|
||||
* and then it would be disadvantageous to use custom tables if
|
||||
* they don't provide sufficient gain to exceed the DAC size.
|
||||
*
|
||||
* On the other hand, I'd consider it as a reasonable result
|
||||
* that the conditioning has no significant influence on the
|
||||
* compression performance. This means that the basic
|
||||
* statistical model is already rather stable.
|
||||
*
|
||||
* Thus, at the moment, we use the default conditioning values
|
||||
* anyway, and do not use the custom formula.
|
||||
*
|
||||
#define CALCULATE_SPECTRAL_CONDITIONING
|
||||
*/
|
||||
|
||||
/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than JLONG.
|
||||
* We assume that int right shift is unsigned if JLONG right shift is,
|
||||
* which should be safe.
|
||||
*/
|
||||
|
||||
#ifdef RIGHT_SHIFT_IS_UNSIGNED
|
||||
#define ISHIFT_TEMPS int ishift_temp;
|
||||
#define IRIGHT_SHIFT(x, shft) \
|
||||
((ishift_temp = (x)) < 0 ? \
|
||||
(ishift_temp >> (shft)) | ((~0) << (16 - (shft))) : \
|
||||
(ishift_temp >> (shft)))
|
||||
#else
|
||||
#define ISHIFT_TEMPS
|
||||
#define IRIGHT_SHIFT(x, shft) ((x) >> (shft))
|
||||
#endif
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_byte(int val, j_compress_ptr cinfo)
|
||||
/* Write next output byte; we do not support suspension in this module. */
|
||||
{
|
||||
struct jpeg_destination_mgr *dest = cinfo->dest;
|
||||
|
||||
*dest->next_output_byte++ = (JOCTET)val;
|
||||
if (--dest->free_in_buffer == 0)
|
||||
if (!(*dest->empty_output_buffer) (cinfo))
|
||||
ERREXIT(cinfo, JERR_CANT_SUSPEND);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Finish up at the end of an arithmetic-compressed scan.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
finish_pass(j_compress_ptr cinfo)
|
||||
{
|
||||
arith_entropy_ptr e = (arith_entropy_ptr)cinfo->entropy;
|
||||
JLONG temp;
|
||||
|
||||
/* Section D.1.8: Termination of encoding */
|
||||
|
||||
/* Find the e->c in the coding interval with the largest
|
||||
* number of trailing zero bits */
|
||||
if ((temp = (e->a - 1 + e->c) & 0xFFFF0000UL) < e->c)
|
||||
e->c = temp + 0x8000L;
|
||||
else
|
||||
e->c = temp;
|
||||
/* Send remaining bytes to output */
|
||||
e->c <<= e->ct;
|
||||
if (e->c & 0xF8000000UL) {
|
||||
/* One final overflow has to be handled */
|
||||
if (e->buffer >= 0) {
|
||||
if (e->zc)
|
||||
do emit_byte(0x00, cinfo);
|
||||
while (--e->zc);
|
||||
emit_byte(e->buffer + 1, cinfo);
|
||||
if (e->buffer + 1 == 0xFF)
|
||||
emit_byte(0x00, cinfo);
|
||||
}
|
||||
e->zc += e->sc; /* carry-over converts stacked 0xFF bytes to 0x00 */
|
||||
e->sc = 0;
|
||||
} else {
|
||||
if (e->buffer == 0)
|
||||
++e->zc;
|
||||
else if (e->buffer >= 0) {
|
||||
if (e->zc)
|
||||
do emit_byte(0x00, cinfo);
|
||||
while (--e->zc);
|
||||
emit_byte(e->buffer, cinfo);
|
||||
}
|
||||
if (e->sc) {
|
||||
if (e->zc)
|
||||
do emit_byte(0x00, cinfo);
|
||||
while (--e->zc);
|
||||
do {
|
||||
emit_byte(0xFF, cinfo);
|
||||
emit_byte(0x00, cinfo);
|
||||
} while (--e->sc);
|
||||
}
|
||||
}
|
||||
/* Output final bytes only if they are not 0x00 */
|
||||
if (e->c & 0x7FFF800L) {
|
||||
if (e->zc) /* output final pending zero bytes */
|
||||
do emit_byte(0x00, cinfo);
|
||||
while (--e->zc);
|
||||
emit_byte((e->c >> 19) & 0xFF, cinfo);
|
||||
if (((e->c >> 19) & 0xFF) == 0xFF)
|
||||
emit_byte(0x00, cinfo);
|
||||
if (e->c & 0x7F800L) {
|
||||
emit_byte((e->c >> 11) & 0xFF, cinfo);
|
||||
if (((e->c >> 11) & 0xFF) == 0xFF)
|
||||
emit_byte(0x00, cinfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The core arithmetic encoding routine (common in JPEG and JBIG).
|
||||
* This needs to go as fast as possible.
|
||||
* Machine-dependent optimization facilities
|
||||
* are not utilized in this portable implementation.
|
||||
* However, this code should be fairly efficient and
|
||||
* may be a good base for further optimizations anyway.
|
||||
*
|
||||
* Parameter 'val' to be encoded may be 0 or 1 (binary decision).
|
||||
*
|
||||
* Note: I've added full "Pacman" termination support to the
|
||||
* byte output routines, which is equivalent to the optional
|
||||
* Discard_final_zeros procedure (Figure D.15) in the spec.
|
||||
* Thus, we always produce the shortest possible output
|
||||
* stream compliant to the spec (no trailing zero bytes,
|
||||
* except for FF stuffing).
|
||||
*
|
||||
* I've also introduced a new scheme for accessing
|
||||
* the probability estimation state machine table,
|
||||
* derived from Markus Kuhn's JBIG implementation.
|
||||
*/
|
||||
|
||||
LOCAL(void)
|
||||
arith_encode(j_compress_ptr cinfo, unsigned char *st, int val)
|
||||
{
|
||||
register arith_entropy_ptr e = (arith_entropy_ptr)cinfo->entropy;
|
||||
register unsigned char nl, nm;
|
||||
register JLONG qe, temp;
|
||||
register int sv;
|
||||
|
||||
/* Fetch values from our compact representation of Table D.2:
|
||||
* Qe values and probability estimation state machine
|
||||
*/
|
||||
sv = *st;
|
||||
qe = jpeg_aritab[sv & 0x7F]; /* => Qe_Value */
|
||||
nl = qe & 0xFF; qe >>= 8; /* Next_Index_LPS + Switch_MPS */
|
||||
nm = qe & 0xFF; qe >>= 8; /* Next_Index_MPS */
|
||||
|
||||
/* Encode & estimation procedures per sections D.1.4 & D.1.5 */
|
||||
e->a -= qe;
|
||||
if (val != (sv >> 7)) {
|
||||
/* Encode the less probable symbol */
|
||||
if (e->a >= qe) {
|
||||
/* If the interval size (qe) for the less probable symbol (LPS)
|
||||
* is larger than the interval size for the MPS, then exchange
|
||||
* the two symbols for coding efficiency, otherwise code the LPS
|
||||
* as usual: */
|
||||
e->c += e->a;
|
||||
e->a = qe;
|
||||
}
|
||||
*st = (sv & 0x80) ^ nl; /* Estimate_after_LPS */
|
||||
} else {
|
||||
/* Encode the more probable symbol */
|
||||
if (e->a >= 0x8000L)
|
||||
return; /* A >= 0x8000 -> ready, no renormalization required */
|
||||
if (e->a < qe) {
|
||||
/* If the interval size (qe) for the less probable symbol (LPS)
|
||||
* is larger than the interval size for the MPS, then exchange
|
||||
* the two symbols for coding efficiency: */
|
||||
e->c += e->a;
|
||||
e->a = qe;
|
||||
}
|
||||
*st = (sv & 0x80) ^ nm; /* Estimate_after_MPS */
|
||||
}
|
||||
|
||||
/* Renormalization & data output per section D.1.6 */
|
||||
do {
|
||||
e->a <<= 1;
|
||||
e->c <<= 1;
|
||||
if (--e->ct == 0) {
|
||||
/* Another byte is ready for output */
|
||||
temp = e->c >> 19;
|
||||
if (temp > 0xFF) {
|
||||
/* Handle overflow over all stacked 0xFF bytes */
|
||||
if (e->buffer >= 0) {
|
||||
if (e->zc)
|
||||
do emit_byte(0x00, cinfo);
|
||||
while (--e->zc);
|
||||
emit_byte(e->buffer + 1, cinfo);
|
||||
if (e->buffer + 1 == 0xFF)
|
||||
emit_byte(0x00, cinfo);
|
||||
}
|
||||
e->zc += e->sc; /* carry-over converts stacked 0xFF bytes to 0x00 */
|
||||
e->sc = 0;
|
||||
/* Note: The 3 spacer bits in the C register guarantee
|
||||
* that the new buffer byte can't be 0xFF here
|
||||
* (see page 160 in the P&M JPEG book). */
|
||||
e->buffer = temp & 0xFF; /* new output byte, might overflow later */
|
||||
} else if (temp == 0xFF) {
|
||||
++e->sc; /* stack 0xFF byte (which might overflow later) */
|
||||
} else {
|
||||
/* Output all stacked 0xFF bytes, they will not overflow any more */
|
||||
if (e->buffer == 0)
|
||||
++e->zc;
|
||||
else if (e->buffer >= 0) {
|
||||
if (e->zc)
|
||||
do emit_byte(0x00, cinfo);
|
||||
while (--e->zc);
|
||||
emit_byte(e->buffer, cinfo);
|
||||
}
|
||||
if (e->sc) {
|
||||
if (e->zc)
|
||||
do emit_byte(0x00, cinfo);
|
||||
while (--e->zc);
|
||||
do {
|
||||
emit_byte(0xFF, cinfo);
|
||||
emit_byte(0x00, cinfo);
|
||||
} while (--e->sc);
|
||||
}
|
||||
e->buffer = temp & 0xFF; /* new output byte (can still overflow) */
|
||||
}
|
||||
e->c &= 0x7FFFFL;
|
||||
e->ct += 8;
|
||||
}
|
||||
} while (e->a < 0x8000L);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Emit a restart marker & resynchronize predictions.
|
||||
*/
|
||||
|
||||
LOCAL(void)
|
||||
emit_restart(j_compress_ptr cinfo, int restart_num)
|
||||
{
|
||||
arith_entropy_ptr entropy = (arith_entropy_ptr)cinfo->entropy;
|
||||
int ci;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
finish_pass(cinfo);
|
||||
|
||||
emit_byte(0xFF, cinfo);
|
||||
emit_byte(JPEG_RST0 + restart_num, cinfo);
|
||||
|
||||
/* Re-initialize statistics areas */
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
/* DC needs no table for refinement scan */
|
||||
if (cinfo->progressive_mode == 0 || (cinfo->Ss == 0 && cinfo->Ah == 0)) {
|
||||
memset(entropy->dc_stats[compptr->dc_tbl_no], 0, DC_STAT_BINS);
|
||||
/* Reset DC predictions to 0 */
|
||||
entropy->last_dc_val[ci] = 0;
|
||||
entropy->dc_context[ci] = 0;
|
||||
}
|
||||
/* AC needs no table when not present */
|
||||
if (cinfo->progressive_mode == 0 || cinfo->Se) {
|
||||
memset(entropy->ac_stats[compptr->ac_tbl_no], 0, AC_STAT_BINS);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset arithmetic encoding variables */
|
||||
entropy->c = 0;
|
||||
entropy->a = 0x10000L;
|
||||
entropy->sc = 0;
|
||||
entropy->zc = 0;
|
||||
entropy->ct = 11;
|
||||
entropy->buffer = -1; /* empty */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* MCU encoding for DC initial scan (either spectral selection,
|
||||
* or first pass of successive approximation).
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
encode_mcu_DC_first(j_compress_ptr cinfo, JBLOCKROW *MCU_data)
|
||||
{
|
||||
arith_entropy_ptr entropy = (arith_entropy_ptr)cinfo->entropy;
|
||||
JBLOCKROW block;
|
||||
unsigned char *st;
|
||||
int blkn, ci, tbl;
|
||||
int v, v2, m;
|
||||
ISHIFT_TEMPS
|
||||
|
||||
/* Emit restart marker if needed */
|
||||
if (cinfo->restart_interval) {
|
||||
if (entropy->restarts_to_go == 0) {
|
||||
emit_restart(cinfo, entropy->next_restart_num);
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
entropy->next_restart_num++;
|
||||
entropy->next_restart_num &= 7;
|
||||
}
|
||||
entropy->restarts_to_go--;
|
||||
}
|
||||
|
||||
/* Encode the MCU data blocks */
|
||||
for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
|
||||
block = MCU_data[blkn];
|
||||
ci = cinfo->MCU_membership[blkn];
|
||||
tbl = cinfo->cur_comp_info[ci]->dc_tbl_no;
|
||||
|
||||
/* Compute the DC value after the required point transform by Al.
|
||||
* This is simply an arithmetic right shift.
|
||||
*/
|
||||
m = IRIGHT_SHIFT((int)((*block)[0]), cinfo->Al);
|
||||
|
||||
/* Sections F.1.4.1 & F.1.4.4.1: Encoding of DC coefficients */
|
||||
|
||||
/* Table F.4: Point to statistics bin S0 for DC coefficient coding */
|
||||
st = entropy->dc_stats[tbl] + entropy->dc_context[ci];
|
||||
|
||||
/* Figure F.4: Encode_DC_DIFF */
|
||||
if ((v = m - entropy->last_dc_val[ci]) == 0) {
|
||||
arith_encode(cinfo, st, 0);
|
||||
entropy->dc_context[ci] = 0; /* zero diff category */
|
||||
} else {
|
||||
entropy->last_dc_val[ci] = m;
|
||||
arith_encode(cinfo, st, 1);
|
||||
/* Figure F.6: Encoding nonzero value v */
|
||||
/* Figure F.7: Encoding the sign of v */
|
||||
if (v > 0) {
|
||||
arith_encode(cinfo, st + 1, 0); /* Table F.4: SS = S0 + 1 */
|
||||
st += 2; /* Table F.4: SP = S0 + 2 */
|
||||
entropy->dc_context[ci] = 4; /* small positive diff category */
|
||||
} else {
|
||||
v = -v;
|
||||
arith_encode(cinfo, st + 1, 1); /* Table F.4: SS = S0 + 1 */
|
||||
st += 3; /* Table F.4: SN = S0 + 3 */
|
||||
entropy->dc_context[ci] = 8; /* small negative diff category */
|
||||
}
|
||||
/* Figure F.8: Encoding the magnitude category of v */
|
||||
m = 0;
|
||||
if (v -= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m = 1;
|
||||
v2 = v;
|
||||
st = entropy->dc_stats[tbl] + 20; /* Table F.4: X1 = 20 */
|
||||
while (v2 >>= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m <<= 1;
|
||||
st += 1;
|
||||
}
|
||||
}
|
||||
arith_encode(cinfo, st, 0);
|
||||
/* Section F.1.4.4.1.2: Establish dc_context conditioning category */
|
||||
if (m < (int)((1L << cinfo->arith_dc_L[tbl]) >> 1))
|
||||
entropy->dc_context[ci] = 0; /* zero diff category */
|
||||
else if (m > (int)((1L << cinfo->arith_dc_U[tbl]) >> 1))
|
||||
entropy->dc_context[ci] += 8; /* large diff category */
|
||||
/* Figure F.9: Encoding the magnitude bit pattern of v */
|
||||
st += 14;
|
||||
while (m >>= 1)
|
||||
arith_encode(cinfo, st, (m & v) ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* MCU encoding for AC initial scan (either spectral selection,
|
||||
* or first pass of successive approximation).
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
encode_mcu_AC_first(j_compress_ptr cinfo, JBLOCKROW *MCU_data)
|
||||
{
|
||||
arith_entropy_ptr entropy = (arith_entropy_ptr)cinfo->entropy;
|
||||
JBLOCKROW block;
|
||||
unsigned char *st;
|
||||
int tbl, k, ke;
|
||||
int v, v2, m;
|
||||
|
||||
/* Emit restart marker if needed */
|
||||
if (cinfo->restart_interval) {
|
||||
if (entropy->restarts_to_go == 0) {
|
||||
emit_restart(cinfo, entropy->next_restart_num);
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
entropy->next_restart_num++;
|
||||
entropy->next_restart_num &= 7;
|
||||
}
|
||||
entropy->restarts_to_go--;
|
||||
}
|
||||
|
||||
/* Encode the MCU data block */
|
||||
block = MCU_data[0];
|
||||
tbl = cinfo->cur_comp_info[0]->ac_tbl_no;
|
||||
|
||||
/* Sections F.1.4.2 & F.1.4.4.2: Encoding of AC coefficients */
|
||||
|
||||
/* Establish EOB (end-of-block) index */
|
||||
for (ke = cinfo->Se; ke > 0; ke--)
|
||||
/* We must apply the point transform by Al. For AC coefficients this
|
||||
* is an integer division with rounding towards 0. To do this portably
|
||||
* in C, we shift after obtaining the absolute value.
|
||||
*/
|
||||
if ((v = (*block)[jpeg_natural_order[ke]]) >= 0) {
|
||||
if (v >>= cinfo->Al) break;
|
||||
} else {
|
||||
v = -v;
|
||||
if (v >>= cinfo->Al) break;
|
||||
}
|
||||
|
||||
/* Figure F.5: Encode_AC_Coefficients */
|
||||
for (k = cinfo->Ss; k <= ke; k++) {
|
||||
st = entropy->ac_stats[tbl] + 3 * (k - 1);
|
||||
arith_encode(cinfo, st, 0); /* EOB decision */
|
||||
for (;;) {
|
||||
if ((v = (*block)[jpeg_natural_order[k]]) >= 0) {
|
||||
if (v >>= cinfo->Al) {
|
||||
arith_encode(cinfo, st + 1, 1);
|
||||
arith_encode(cinfo, entropy->fixed_bin, 0);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
v = -v;
|
||||
if (v >>= cinfo->Al) {
|
||||
arith_encode(cinfo, st + 1, 1);
|
||||
arith_encode(cinfo, entropy->fixed_bin, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
arith_encode(cinfo, st + 1, 0); st += 3; k++;
|
||||
}
|
||||
st += 2;
|
||||
/* Figure F.8: Encoding the magnitude category of v */
|
||||
m = 0;
|
||||
if (v -= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m = 1;
|
||||
v2 = v;
|
||||
if (v2 >>= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m <<= 1;
|
||||
st = entropy->ac_stats[tbl] +
|
||||
(k <= cinfo->arith_ac_K[tbl] ? 189 : 217);
|
||||
while (v2 >>= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m <<= 1;
|
||||
st += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
arith_encode(cinfo, st, 0);
|
||||
/* Figure F.9: Encoding the magnitude bit pattern of v */
|
||||
st += 14;
|
||||
while (m >>= 1)
|
||||
arith_encode(cinfo, st, (m & v) ? 1 : 0);
|
||||
}
|
||||
/* Encode EOB decision only if k <= cinfo->Se */
|
||||
if (k <= cinfo->Se) {
|
||||
st = entropy->ac_stats[tbl] + 3 * (k - 1);
|
||||
arith_encode(cinfo, st, 1);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* MCU encoding for DC successive approximation refinement scan.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
encode_mcu_DC_refine(j_compress_ptr cinfo, JBLOCKROW *MCU_data)
|
||||
{
|
||||
arith_entropy_ptr entropy = (arith_entropy_ptr)cinfo->entropy;
|
||||
unsigned char *st;
|
||||
int Al, blkn;
|
||||
|
||||
/* Emit restart marker if needed */
|
||||
if (cinfo->restart_interval) {
|
||||
if (entropy->restarts_to_go == 0) {
|
||||
emit_restart(cinfo, entropy->next_restart_num);
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
entropy->next_restart_num++;
|
||||
entropy->next_restart_num &= 7;
|
||||
}
|
||||
entropy->restarts_to_go--;
|
||||
}
|
||||
|
||||
st = entropy->fixed_bin; /* use fixed probability estimation */
|
||||
Al = cinfo->Al;
|
||||
|
||||
/* Encode the MCU data blocks */
|
||||
for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
|
||||
/* We simply emit the Al'th bit of the DC coefficient value. */
|
||||
arith_encode(cinfo, st, (MCU_data[blkn][0][0] >> Al) & 1);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* MCU encoding for AC successive approximation refinement scan.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
encode_mcu_AC_refine(j_compress_ptr cinfo, JBLOCKROW *MCU_data)
|
||||
{
|
||||
arith_entropy_ptr entropy = (arith_entropy_ptr)cinfo->entropy;
|
||||
JBLOCKROW block;
|
||||
unsigned char *st;
|
||||
int tbl, k, ke, kex;
|
||||
int v;
|
||||
|
||||
/* Emit restart marker if needed */
|
||||
if (cinfo->restart_interval) {
|
||||
if (entropy->restarts_to_go == 0) {
|
||||
emit_restart(cinfo, entropy->next_restart_num);
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
entropy->next_restart_num++;
|
||||
entropy->next_restart_num &= 7;
|
||||
}
|
||||
entropy->restarts_to_go--;
|
||||
}
|
||||
|
||||
/* Encode the MCU data block */
|
||||
block = MCU_data[0];
|
||||
tbl = cinfo->cur_comp_info[0]->ac_tbl_no;
|
||||
|
||||
/* Section G.1.3.3: Encoding of AC coefficients */
|
||||
|
||||
/* Establish EOB (end-of-block) index */
|
||||
for (ke = cinfo->Se; ke > 0; ke--)
|
||||
/* We must apply the point transform by Al. For AC coefficients this
|
||||
* is an integer division with rounding towards 0. To do this portably
|
||||
* in C, we shift after obtaining the absolute value.
|
||||
*/
|
||||
if ((v = (*block)[jpeg_natural_order[ke]]) >= 0) {
|
||||
if (v >>= cinfo->Al) break;
|
||||
} else {
|
||||
v = -v;
|
||||
if (v >>= cinfo->Al) break;
|
||||
}
|
||||
|
||||
/* Establish EOBx (previous stage end-of-block) index */
|
||||
for (kex = ke; kex > 0; kex--)
|
||||
if ((v = (*block)[jpeg_natural_order[kex]]) >= 0) {
|
||||
if (v >>= cinfo->Ah) break;
|
||||
} else {
|
||||
v = -v;
|
||||
if (v >>= cinfo->Ah) break;
|
||||
}
|
||||
|
||||
/* Figure G.10: Encode_AC_Coefficients_SA */
|
||||
for (k = cinfo->Ss; k <= ke; k++) {
|
||||
st = entropy->ac_stats[tbl] + 3 * (k - 1);
|
||||
if (k > kex)
|
||||
arith_encode(cinfo, st, 0); /* EOB decision */
|
||||
for (;;) {
|
||||
if ((v = (*block)[jpeg_natural_order[k]]) >= 0) {
|
||||
if (v >>= cinfo->Al) {
|
||||
if (v >> 1) /* previously nonzero coef */
|
||||
arith_encode(cinfo, st + 2, (v & 1));
|
||||
else { /* newly nonzero coef */
|
||||
arith_encode(cinfo, st + 1, 1);
|
||||
arith_encode(cinfo, entropy->fixed_bin, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
v = -v;
|
||||
if (v >>= cinfo->Al) {
|
||||
if (v >> 1) /* previously nonzero coef */
|
||||
arith_encode(cinfo, st + 2, (v & 1));
|
||||
else { /* newly nonzero coef */
|
||||
arith_encode(cinfo, st + 1, 1);
|
||||
arith_encode(cinfo, entropy->fixed_bin, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
arith_encode(cinfo, st + 1, 0); st += 3; k++;
|
||||
}
|
||||
}
|
||||
/* Encode EOB decision only if k <= cinfo->Se */
|
||||
if (k <= cinfo->Se) {
|
||||
st = entropy->ac_stats[tbl] + 3 * (k - 1);
|
||||
arith_encode(cinfo, st, 1);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Encode and output one MCU's worth of arithmetic-compressed coefficients.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
encode_mcu(j_compress_ptr cinfo, JBLOCKROW *MCU_data)
|
||||
{
|
||||
arith_entropy_ptr entropy = (arith_entropy_ptr)cinfo->entropy;
|
||||
jpeg_component_info *compptr;
|
||||
JBLOCKROW block;
|
||||
unsigned char *st;
|
||||
int blkn, ci, tbl, k, ke;
|
||||
int v, v2, m;
|
||||
|
||||
/* Emit restart marker if needed */
|
||||
if (cinfo->restart_interval) {
|
||||
if (entropy->restarts_to_go == 0) {
|
||||
emit_restart(cinfo, entropy->next_restart_num);
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
entropy->next_restart_num++;
|
||||
entropy->next_restart_num &= 7;
|
||||
}
|
||||
entropy->restarts_to_go--;
|
||||
}
|
||||
|
||||
/* Encode the MCU data blocks */
|
||||
for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) {
|
||||
block = MCU_data[blkn];
|
||||
ci = cinfo->MCU_membership[blkn];
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
|
||||
/* Sections F.1.4.1 & F.1.4.4.1: Encoding of DC coefficients */
|
||||
|
||||
tbl = compptr->dc_tbl_no;
|
||||
|
||||
/* Table F.4: Point to statistics bin S0 for DC coefficient coding */
|
||||
st = entropy->dc_stats[tbl] + entropy->dc_context[ci];
|
||||
|
||||
/* Figure F.4: Encode_DC_DIFF */
|
||||
if ((v = (*block)[0] - entropy->last_dc_val[ci]) == 0) {
|
||||
arith_encode(cinfo, st, 0);
|
||||
entropy->dc_context[ci] = 0; /* zero diff category */
|
||||
} else {
|
||||
entropy->last_dc_val[ci] = (*block)[0];
|
||||
arith_encode(cinfo, st, 1);
|
||||
/* Figure F.6: Encoding nonzero value v */
|
||||
/* Figure F.7: Encoding the sign of v */
|
||||
if (v > 0) {
|
||||
arith_encode(cinfo, st + 1, 0); /* Table F.4: SS = S0 + 1 */
|
||||
st += 2; /* Table F.4: SP = S0 + 2 */
|
||||
entropy->dc_context[ci] = 4; /* small positive diff category */
|
||||
} else {
|
||||
v = -v;
|
||||
arith_encode(cinfo, st + 1, 1); /* Table F.4: SS = S0 + 1 */
|
||||
st += 3; /* Table F.4: SN = S0 + 3 */
|
||||
entropy->dc_context[ci] = 8; /* small negative diff category */
|
||||
}
|
||||
/* Figure F.8: Encoding the magnitude category of v */
|
||||
m = 0;
|
||||
if (v -= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m = 1;
|
||||
v2 = v;
|
||||
st = entropy->dc_stats[tbl] + 20; /* Table F.4: X1 = 20 */
|
||||
while (v2 >>= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m <<= 1;
|
||||
st += 1;
|
||||
}
|
||||
}
|
||||
arith_encode(cinfo, st, 0);
|
||||
/* Section F.1.4.4.1.2: Establish dc_context conditioning category */
|
||||
if (m < (int)((1L << cinfo->arith_dc_L[tbl]) >> 1))
|
||||
entropy->dc_context[ci] = 0; /* zero diff category */
|
||||
else if (m > (int)((1L << cinfo->arith_dc_U[tbl]) >> 1))
|
||||
entropy->dc_context[ci] += 8; /* large diff category */
|
||||
/* Figure F.9: Encoding the magnitude bit pattern of v */
|
||||
st += 14;
|
||||
while (m >>= 1)
|
||||
arith_encode(cinfo, st, (m & v) ? 1 : 0);
|
||||
}
|
||||
|
||||
/* Sections F.1.4.2 & F.1.4.4.2: Encoding of AC coefficients */
|
||||
|
||||
tbl = compptr->ac_tbl_no;
|
||||
|
||||
/* Establish EOB (end-of-block) index */
|
||||
for (ke = DCTSIZE2 - 1; ke > 0; ke--)
|
||||
if ((*block)[jpeg_natural_order[ke]]) break;
|
||||
|
||||
/* Figure F.5: Encode_AC_Coefficients */
|
||||
for (k = 1; k <= ke; k++) {
|
||||
st = entropy->ac_stats[tbl] + 3 * (k - 1);
|
||||
arith_encode(cinfo, st, 0); /* EOB decision */
|
||||
while ((v = (*block)[jpeg_natural_order[k]]) == 0) {
|
||||
arith_encode(cinfo, st + 1, 0); st += 3; k++;
|
||||
}
|
||||
arith_encode(cinfo, st + 1, 1);
|
||||
/* Figure F.6: Encoding nonzero value v */
|
||||
/* Figure F.7: Encoding the sign of v */
|
||||
if (v > 0) {
|
||||
arith_encode(cinfo, entropy->fixed_bin, 0);
|
||||
} else {
|
||||
v = -v;
|
||||
arith_encode(cinfo, entropy->fixed_bin, 1);
|
||||
}
|
||||
st += 2;
|
||||
/* Figure F.8: Encoding the magnitude category of v */
|
||||
m = 0;
|
||||
if (v -= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m = 1;
|
||||
v2 = v;
|
||||
if (v2 >>= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m <<= 1;
|
||||
st = entropy->ac_stats[tbl] +
|
||||
(k <= cinfo->arith_ac_K[tbl] ? 189 : 217);
|
||||
while (v2 >>= 1) {
|
||||
arith_encode(cinfo, st, 1);
|
||||
m <<= 1;
|
||||
st += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
arith_encode(cinfo, st, 0);
|
||||
/* Figure F.9: Encoding the magnitude bit pattern of v */
|
||||
st += 14;
|
||||
while (m >>= 1)
|
||||
arith_encode(cinfo, st, (m & v) ? 1 : 0);
|
||||
}
|
||||
/* Encode EOB decision only if k <= DCTSIZE2 - 1 */
|
||||
if (k <= DCTSIZE2 - 1) {
|
||||
st = entropy->ac_stats[tbl] + 3 * (k - 1);
|
||||
arith_encode(cinfo, st, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize for an arithmetic-compressed scan.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
start_pass(j_compress_ptr cinfo, boolean gather_statistics)
|
||||
{
|
||||
arith_entropy_ptr entropy = (arith_entropy_ptr)cinfo->entropy;
|
||||
int ci, tbl;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
if (gather_statistics)
|
||||
/* Make sure to avoid that in the master control logic!
|
||||
* We are fully adaptive here and need no extra
|
||||
* statistics gathering pass!
|
||||
*/
|
||||
ERREXIT(cinfo, JERR_NOTIMPL);
|
||||
|
||||
/* We assume jcmaster.c already validated the progressive scan parameters. */
|
||||
|
||||
/* Select execution routines */
|
||||
if (cinfo->progressive_mode) {
|
||||
if (cinfo->Ah == 0) {
|
||||
if (cinfo->Ss == 0)
|
||||
entropy->pub.encode_mcu = encode_mcu_DC_first;
|
||||
else
|
||||
entropy->pub.encode_mcu = encode_mcu_AC_first;
|
||||
} else {
|
||||
if (cinfo->Ss == 0)
|
||||
entropy->pub.encode_mcu = encode_mcu_DC_refine;
|
||||
else
|
||||
entropy->pub.encode_mcu = encode_mcu_AC_refine;
|
||||
}
|
||||
} else
|
||||
entropy->pub.encode_mcu = encode_mcu;
|
||||
|
||||
/* Allocate & initialize requested statistics areas */
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
/* DC needs no table for refinement scan */
|
||||
if (cinfo->progressive_mode == 0 || (cinfo->Ss == 0 && cinfo->Ah == 0)) {
|
||||
tbl = compptr->dc_tbl_no;
|
||||
if (tbl < 0 || tbl >= NUM_ARITH_TBLS)
|
||||
ERREXIT1(cinfo, JERR_NO_ARITH_TABLE, tbl);
|
||||
if (entropy->dc_stats[tbl] == NULL)
|
||||
entropy->dc_stats[tbl] = (unsigned char *)(*cinfo->mem->alloc_small)
|
||||
((j_common_ptr)cinfo, JPOOL_IMAGE, DC_STAT_BINS);
|
||||
memset(entropy->dc_stats[tbl], 0, DC_STAT_BINS);
|
||||
/* Initialize DC predictions to 0 */
|
||||
entropy->last_dc_val[ci] = 0;
|
||||
entropy->dc_context[ci] = 0;
|
||||
}
|
||||
/* AC needs no table when not present */
|
||||
if (cinfo->progressive_mode == 0 || cinfo->Se) {
|
||||
tbl = compptr->ac_tbl_no;
|
||||
if (tbl < 0 || tbl >= NUM_ARITH_TBLS)
|
||||
ERREXIT1(cinfo, JERR_NO_ARITH_TABLE, tbl);
|
||||
if (entropy->ac_stats[tbl] == NULL)
|
||||
entropy->ac_stats[tbl] = (unsigned char *)(*cinfo->mem->alloc_small)
|
||||
((j_common_ptr)cinfo, JPOOL_IMAGE, AC_STAT_BINS);
|
||||
memset(entropy->ac_stats[tbl], 0, AC_STAT_BINS);
|
||||
#ifdef CALCULATE_SPECTRAL_CONDITIONING
|
||||
if (cinfo->progressive_mode)
|
||||
/* Section G.1.3.2: Set appropriate arithmetic conditioning value Kx */
|
||||
cinfo->arith_ac_K[tbl] = cinfo->Ss +
|
||||
((8 + cinfo->Se - cinfo->Ss) >> 4);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize arithmetic encoding variables */
|
||||
entropy->c = 0;
|
||||
entropy->a = 0x10000L;
|
||||
entropy->sc = 0;
|
||||
entropy->zc = 0;
|
||||
entropy->ct = 11;
|
||||
entropy->buffer = -1; /* empty */
|
||||
|
||||
/* Initialize restart stuff */
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
entropy->next_restart_num = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Module initialization routine for arithmetic entropy encoding.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jinit_arith_encoder(j_compress_ptr cinfo)
|
||||
{
|
||||
arith_entropy_ptr entropy;
|
||||
int i;
|
||||
|
||||
entropy = (arith_entropy_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(arith_entropy_encoder));
|
||||
cinfo->entropy = (struct jpeg_entropy_encoder *)entropy;
|
||||
entropy->pub.start_pass = start_pass;
|
||||
entropy->pub.finish_pass = finish_pass;
|
||||
|
||||
/* Mark tables unallocated */
|
||||
for (i = 0; i < NUM_ARITH_TBLS; i++) {
|
||||
entropy->dc_stats[i] = NULL;
|
||||
entropy->ac_stats[i] = NULL;
|
||||
}
|
||||
|
||||
/* Initialize index for fixed probability estimation */
|
||||
entropy->fixed_bin[0] = 113;
|
||||
}
|
||||
+454
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
* jccoefct.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1994-1997, Thomas G. Lane.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains the coefficient buffer controller for compression.
|
||||
* This controller is the top level of the lossy JPEG compressor proper.
|
||||
* The coefficient buffer lies between forward-DCT and entropy encoding steps.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jsamplecomp.h"
|
||||
|
||||
|
||||
/* We use a full-image coefficient buffer when doing Huffman optimization,
|
||||
* and also for writing multiple-scan JPEG files. In all cases, the DCT
|
||||
* step is run during the first pass, and subsequent passes need only read
|
||||
* the buffered coefficients.
|
||||
*/
|
||||
#ifdef ENTROPY_OPT_SUPPORTED
|
||||
#define FULL_COEF_BUFFER_SUPPORTED
|
||||
#else
|
||||
#ifdef C_MULTISCAN_FILES_SUPPORTED
|
||||
#define FULL_COEF_BUFFER_SUPPORTED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
/* Private buffer controller object */
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_c_coef_controller pub; /* public fields */
|
||||
|
||||
JDIMENSION iMCU_row_num; /* iMCU row # within image */
|
||||
JDIMENSION mcu_ctr; /* counts MCUs processed in current row */
|
||||
int MCU_vert_offset; /* counts MCU rows within iMCU row */
|
||||
int MCU_rows_per_iMCU_row; /* number of such rows needed */
|
||||
|
||||
/* For single-pass compression, it's sufficient to buffer just one MCU
|
||||
* (although this may prove a bit slow in practice). We allocate a
|
||||
* workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each
|
||||
* MCU constructed and sent. In multi-pass modes, this array points to the
|
||||
* current MCU's blocks within the virtual arrays.
|
||||
*/
|
||||
JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU];
|
||||
|
||||
/* In multi-pass modes, we need a virtual block array for each component. */
|
||||
jvirt_barray_ptr whole_image[MAX_COMPONENTS];
|
||||
} my_coef_controller;
|
||||
|
||||
typedef my_coef_controller *my_coef_ptr;
|
||||
|
||||
|
||||
/* Forward declarations */
|
||||
METHODDEF(boolean) compress_data(j_compress_ptr cinfo, _JSAMPIMAGE input_buf);
|
||||
#ifdef FULL_COEF_BUFFER_SUPPORTED
|
||||
METHODDEF(boolean) compress_first_pass(j_compress_ptr cinfo,
|
||||
_JSAMPIMAGE input_buf);
|
||||
METHODDEF(boolean) compress_output(j_compress_ptr cinfo,
|
||||
_JSAMPIMAGE input_buf);
|
||||
#endif
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
start_iMCU_row(j_compress_ptr cinfo)
|
||||
/* Reset within-iMCU-row counters for a new row */
|
||||
{
|
||||
my_coef_ptr coef = (my_coef_ptr)cinfo->coef;
|
||||
|
||||
/* In an interleaved scan, an MCU row is the same as an iMCU row.
|
||||
* In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows.
|
||||
* But at the bottom of the image, process only what's left.
|
||||
*/
|
||||
if (cinfo->comps_in_scan > 1) {
|
||||
coef->MCU_rows_per_iMCU_row = 1;
|
||||
} else {
|
||||
if (coef->iMCU_row_num < (cinfo->total_iMCU_rows - 1))
|
||||
coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor;
|
||||
else
|
||||
coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height;
|
||||
}
|
||||
|
||||
coef->mcu_ctr = 0;
|
||||
coef->MCU_vert_offset = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize for a processing pass.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
start_pass_coef(j_compress_ptr cinfo, J_BUF_MODE pass_mode)
|
||||
{
|
||||
my_coef_ptr coef = (my_coef_ptr)cinfo->coef;
|
||||
|
||||
coef->iMCU_row_num = 0;
|
||||
start_iMCU_row(cinfo);
|
||||
|
||||
switch (pass_mode) {
|
||||
case JBUF_PASS_THRU:
|
||||
if (coef->whole_image[0] != NULL)
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
coef->pub._compress_data = compress_data;
|
||||
break;
|
||||
#ifdef FULL_COEF_BUFFER_SUPPORTED
|
||||
case JBUF_SAVE_AND_PASS:
|
||||
if (coef->whole_image[0] == NULL)
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
coef->pub._compress_data = compress_first_pass;
|
||||
break;
|
||||
case JBUF_CRANK_DEST:
|
||||
if (coef->whole_image[0] == NULL)
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
coef->pub._compress_data = compress_output;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Process some data in the single-pass case.
|
||||
* We process the equivalent of one fully interleaved MCU row ("iMCU" row)
|
||||
* per call, ie, v_samp_factor block rows for each component in the image.
|
||||
* Returns TRUE if the iMCU row is completed, FALSE if suspended.
|
||||
*
|
||||
* NB: input_buf contains a plane for each component in image,
|
||||
* which we index according to the component's SOF position.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
compress_data(j_compress_ptr cinfo, _JSAMPIMAGE input_buf)
|
||||
{
|
||||
my_coef_ptr coef = (my_coef_ptr)cinfo->coef;
|
||||
JDIMENSION MCU_col_num; /* index of current MCU within row */
|
||||
JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1;
|
||||
JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
|
||||
int blkn, bi, ci, yindex, yoffset, blockcnt;
|
||||
JDIMENSION ypos, xpos;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
/* Loop to write as much as one whole iMCU row */
|
||||
for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row;
|
||||
yoffset++) {
|
||||
for (MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col;
|
||||
MCU_col_num++) {
|
||||
/* Determine where data comes from in input_buf and do the DCT thing.
|
||||
* Each call on forward_DCT processes a horizontal row of DCT blocks
|
||||
* as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks
|
||||
* sequentially. Dummy blocks at the right or bottom edge are filled in
|
||||
* specially. The data in them does not matter for image reconstruction,
|
||||
* so we fill them with values that will encode to the smallest amount of
|
||||
* data, viz: all zeroes in the AC entries, DC entries equal to previous
|
||||
* block's DC value. (Thanks to Thomas Kinsman for this idea.)
|
||||
*/
|
||||
blkn = 0;
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width :
|
||||
compptr->last_col_width;
|
||||
xpos = MCU_col_num * compptr->MCU_sample_width;
|
||||
ypos = yoffset * DCTSIZE; /* ypos == (yoffset+yindex) * DCTSIZE */
|
||||
for (yindex = 0; yindex < compptr->MCU_height; yindex++) {
|
||||
if (coef->iMCU_row_num < last_iMCU_row ||
|
||||
yoffset + yindex < compptr->last_row_height) {
|
||||
(*cinfo->fdct->_forward_DCT) (cinfo, compptr,
|
||||
input_buf[compptr->component_index],
|
||||
coef->MCU_buffer[blkn],
|
||||
ypos, xpos, (JDIMENSION)blockcnt);
|
||||
if (blockcnt < compptr->MCU_width) {
|
||||
/* Create some dummy blocks at the right edge of the image. */
|
||||
jzero_far((void *)coef->MCU_buffer[blkn + blockcnt],
|
||||
(compptr->MCU_width - blockcnt) * sizeof(JBLOCK));
|
||||
for (bi = blockcnt; bi < compptr->MCU_width; bi++) {
|
||||
coef->MCU_buffer[blkn + bi][0][0] =
|
||||
coef->MCU_buffer[blkn + bi - 1][0][0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Create a row of dummy blocks at the bottom of the image. */
|
||||
jzero_far((void *)coef->MCU_buffer[blkn],
|
||||
compptr->MCU_width * sizeof(JBLOCK));
|
||||
for (bi = 0; bi < compptr->MCU_width; bi++) {
|
||||
coef->MCU_buffer[blkn + bi][0][0] =
|
||||
coef->MCU_buffer[blkn - 1][0][0];
|
||||
}
|
||||
}
|
||||
blkn += compptr->MCU_width;
|
||||
ypos += DCTSIZE;
|
||||
}
|
||||
}
|
||||
/* Try to write the MCU. In event of a suspension failure, we will
|
||||
* re-DCT the MCU on restart (a bit inefficient, could be fixed...)
|
||||
*/
|
||||
if (!(*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) {
|
||||
/* Suspension forced; update state counters and exit */
|
||||
coef->MCU_vert_offset = yoffset;
|
||||
coef->mcu_ctr = MCU_col_num;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
/* Completed an MCU row, but perhaps not an iMCU row */
|
||||
coef->mcu_ctr = 0;
|
||||
}
|
||||
/* Completed the iMCU row, advance counters for next one */
|
||||
coef->iMCU_row_num++;
|
||||
start_iMCU_row(cinfo);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
#ifdef FULL_COEF_BUFFER_SUPPORTED
|
||||
|
||||
/*
|
||||
* Process some data in the first pass of a multi-pass case.
|
||||
* We process the equivalent of one fully interleaved MCU row ("iMCU" row)
|
||||
* per call, ie, v_samp_factor block rows for each component in the image.
|
||||
* This amount of data is read from the source buffer, DCT'd and quantized,
|
||||
* and saved into the virtual arrays. We also generate suitable dummy blocks
|
||||
* as needed at the right and lower edges. (The dummy blocks are constructed
|
||||
* in the virtual arrays, which have been padded appropriately.) This makes
|
||||
* it possible for subsequent passes not to worry about real vs. dummy blocks.
|
||||
*
|
||||
* We must also emit the data to the entropy encoder. This is conveniently
|
||||
* done by calling compress_output() after we've loaded the current strip
|
||||
* of the virtual arrays.
|
||||
*
|
||||
* NB: input_buf contains a plane for each component in image. All
|
||||
* components are DCT'd and loaded into the virtual arrays in this pass.
|
||||
* However, it may be that only a subset of the components are emitted to
|
||||
* the entropy encoder during this first pass; be careful about looking
|
||||
* at the scan-dependent variables (MCU dimensions, etc).
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
compress_first_pass(j_compress_ptr cinfo, _JSAMPIMAGE input_buf)
|
||||
{
|
||||
my_coef_ptr coef = (my_coef_ptr)cinfo->coef;
|
||||
JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
|
||||
JDIMENSION blocks_across, MCUs_across, MCUindex;
|
||||
int bi, ci, h_samp_factor, block_row, block_rows, ndummy;
|
||||
JCOEF lastDC;
|
||||
jpeg_component_info *compptr;
|
||||
JBLOCKARRAY buffer;
|
||||
JBLOCKROW thisblockrow, lastblockrow;
|
||||
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
/* Align the virtual buffer for this component. */
|
||||
buffer = (*cinfo->mem->access_virt_barray)
|
||||
((j_common_ptr)cinfo, coef->whole_image[ci],
|
||||
coef->iMCU_row_num * compptr->v_samp_factor,
|
||||
(JDIMENSION)compptr->v_samp_factor, TRUE);
|
||||
/* Count non-dummy DCT block rows in this iMCU row. */
|
||||
if (coef->iMCU_row_num < last_iMCU_row)
|
||||
block_rows = compptr->v_samp_factor;
|
||||
else {
|
||||
/* NB: can't use last_row_height here, since may not be set! */
|
||||
block_rows = (int)(compptr->height_in_blocks % compptr->v_samp_factor);
|
||||
if (block_rows == 0) block_rows = compptr->v_samp_factor;
|
||||
}
|
||||
blocks_across = compptr->width_in_blocks;
|
||||
h_samp_factor = compptr->h_samp_factor;
|
||||
/* Count number of dummy blocks to be added at the right margin. */
|
||||
ndummy = (int)(blocks_across % h_samp_factor);
|
||||
if (ndummy > 0)
|
||||
ndummy = h_samp_factor - ndummy;
|
||||
/* Perform DCT for all non-dummy blocks in this iMCU row. Each call
|
||||
* on forward_DCT processes a complete horizontal row of DCT blocks.
|
||||
*/
|
||||
for (block_row = 0; block_row < block_rows; block_row++) {
|
||||
thisblockrow = buffer[block_row];
|
||||
(*cinfo->fdct->_forward_DCT) (cinfo, compptr,
|
||||
input_buf[ci], thisblockrow,
|
||||
(JDIMENSION)(block_row * DCTSIZE),
|
||||
(JDIMENSION)0, blocks_across);
|
||||
if (ndummy > 0) {
|
||||
/* Create dummy blocks at the right edge of the image. */
|
||||
thisblockrow += blocks_across; /* => first dummy block */
|
||||
jzero_far((void *)thisblockrow, ndummy * sizeof(JBLOCK));
|
||||
lastDC = thisblockrow[-1][0];
|
||||
for (bi = 0; bi < ndummy; bi++) {
|
||||
thisblockrow[bi][0] = lastDC;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* If at end of image, create dummy block rows as needed.
|
||||
* The tricky part here is that within each MCU, we want the DC values
|
||||
* of the dummy blocks to match the last real block's DC value.
|
||||
* This squeezes a few more bytes out of the resulting file...
|
||||
*/
|
||||
if (coef->iMCU_row_num == last_iMCU_row) {
|
||||
blocks_across += ndummy; /* include lower right corner */
|
||||
MCUs_across = blocks_across / h_samp_factor;
|
||||
for (block_row = block_rows; block_row < compptr->v_samp_factor;
|
||||
block_row++) {
|
||||
thisblockrow = buffer[block_row];
|
||||
lastblockrow = buffer[block_row - 1];
|
||||
jzero_far((void *)thisblockrow,
|
||||
(size_t)(blocks_across * sizeof(JBLOCK)));
|
||||
for (MCUindex = 0; MCUindex < MCUs_across; MCUindex++) {
|
||||
lastDC = lastblockrow[h_samp_factor - 1][0];
|
||||
for (bi = 0; bi < h_samp_factor; bi++) {
|
||||
thisblockrow[bi][0] = lastDC;
|
||||
}
|
||||
thisblockrow += h_samp_factor; /* advance to next MCU in row */
|
||||
lastblockrow += h_samp_factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* NB: compress_output will increment iMCU_row_num if successful.
|
||||
* A suspension return will result in redoing all the work above next time.
|
||||
*/
|
||||
|
||||
/* Emit data to the entropy encoder, sharing code with subsequent passes */
|
||||
return compress_output(cinfo, input_buf);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Process some data in subsequent passes of a multi-pass case.
|
||||
* We process the equivalent of one fully interleaved MCU row ("iMCU" row)
|
||||
* per call, ie, v_samp_factor block rows for each component in the scan.
|
||||
* The data is obtained from the virtual arrays and fed to the entropy coder.
|
||||
* Returns TRUE if the iMCU row is completed, FALSE if suspended.
|
||||
*
|
||||
* NB: input_buf is ignored; it is likely to be a NULL pointer.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
compress_output(j_compress_ptr cinfo, _JSAMPIMAGE input_buf)
|
||||
{
|
||||
my_coef_ptr coef = (my_coef_ptr)cinfo->coef;
|
||||
JDIMENSION MCU_col_num; /* index of current MCU within row */
|
||||
int blkn, ci, xindex, yindex, yoffset;
|
||||
JDIMENSION start_col;
|
||||
JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN];
|
||||
JBLOCKROW buffer_ptr;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
/* Align the virtual buffers for the components used in this scan.
|
||||
* NB: during first pass, this is safe only because the buffers will
|
||||
* already be aligned properly, so jmemmgr.c won't need to do any I/O.
|
||||
*/
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
buffer[ci] = (*cinfo->mem->access_virt_barray)
|
||||
((j_common_ptr)cinfo, coef->whole_image[compptr->component_index],
|
||||
coef->iMCU_row_num * compptr->v_samp_factor,
|
||||
(JDIMENSION)compptr->v_samp_factor, FALSE);
|
||||
}
|
||||
|
||||
/* Loop to process one whole iMCU row */
|
||||
for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row;
|
||||
yoffset++) {
|
||||
for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row;
|
||||
MCU_col_num++) {
|
||||
/* Construct list of pointers to DCT blocks belonging to this MCU */
|
||||
blkn = 0; /* index of current DCT block within MCU */
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
start_col = MCU_col_num * compptr->MCU_width;
|
||||
for (yindex = 0; yindex < compptr->MCU_height; yindex++) {
|
||||
buffer_ptr = buffer[ci][yindex + yoffset] + start_col;
|
||||
for (xindex = 0; xindex < compptr->MCU_width; xindex++) {
|
||||
coef->MCU_buffer[blkn++] = buffer_ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Try to write the MCU. */
|
||||
if (!(*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) {
|
||||
/* Suspension forced; update state counters and exit */
|
||||
coef->MCU_vert_offset = yoffset;
|
||||
coef->mcu_ctr = MCU_col_num;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
/* Completed an MCU row, but perhaps not an iMCU row */
|
||||
coef->mcu_ctr = 0;
|
||||
}
|
||||
/* Completed the iMCU row, advance counters for next one */
|
||||
coef->iMCU_row_num++;
|
||||
start_iMCU_row(cinfo);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#endif /* FULL_COEF_BUFFER_SUPPORTED */
|
||||
|
||||
|
||||
/*
|
||||
* Initialize coefficient buffer controller.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
_jinit_c_coef_controller(j_compress_ptr cinfo, boolean need_full_buffer)
|
||||
{
|
||||
my_coef_ptr coef;
|
||||
|
||||
if (cinfo->data_precision != BITS_IN_JSAMPLE)
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
|
||||
coef = (my_coef_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(my_coef_controller));
|
||||
cinfo->coef = (struct jpeg_c_coef_controller *)coef;
|
||||
coef->pub.start_pass = start_pass_coef;
|
||||
|
||||
/* Create the coefficient buffer. */
|
||||
if (need_full_buffer) {
|
||||
#ifdef FULL_COEF_BUFFER_SUPPORTED
|
||||
/* Allocate a full-image virtual array for each component, */
|
||||
/* padded to a multiple of samp_factor DCT blocks in each direction. */
|
||||
int ci;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
coef->whole_image[ci] = (*cinfo->mem->request_virt_barray)
|
||||
((j_common_ptr)cinfo, JPOOL_IMAGE, FALSE,
|
||||
(JDIMENSION)jround_up((long)compptr->width_in_blocks,
|
||||
(long)compptr->h_samp_factor),
|
||||
(JDIMENSION)jround_up((long)compptr->height_in_blocks,
|
||||
(long)compptr->v_samp_factor),
|
||||
(JDIMENSION)compptr->v_samp_factor);
|
||||
}
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
#endif
|
||||
} else {
|
||||
/* We only need a single-MCU buffer. */
|
||||
JBLOCKROW buffer;
|
||||
int i;
|
||||
|
||||
buffer = (JBLOCKROW)
|
||||
(*cinfo->mem->alloc_large) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
C_MAX_BLOCKS_IN_MCU * sizeof(JBLOCK));
|
||||
for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) {
|
||||
coef->MCU_buffer[i] = buffer + i;
|
||||
}
|
||||
coef->whole_image[0] = NULL; /* flag for no virtual arrays */
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* jccolext.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1991-1996, Thomas G. Lane.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2009-2012, 2015, 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains input colorspace conversion routines.
|
||||
*/
|
||||
|
||||
|
||||
/* This file is included by jccolor.c */
|
||||
|
||||
|
||||
/*
|
||||
* Convert some rows of samples to the JPEG colorspace.
|
||||
*
|
||||
* Note that we change from the application's interleaved-pixel format
|
||||
* to our internal noninterleaved, one-plane-per-component format.
|
||||
* The input buffer is therefore three times as wide as the output buffer.
|
||||
*
|
||||
* A starting row offset is provided only for the output buffer. The caller
|
||||
* can easily adjust the passed input_buf value to accommodate any row
|
||||
* offset required on that side.
|
||||
*/
|
||||
|
||||
INLINE
|
||||
LOCAL(void)
|
||||
rgb_ycc_convert_internal(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row,
|
||||
int num_rows)
|
||||
{
|
||||
#if BITS_IN_JSAMPLE != 16
|
||||
my_cconvert_ptr cconvert = (my_cconvert_ptr)cinfo->cconvert;
|
||||
register int r, g, b;
|
||||
register JLONG *ctab = cconvert->rgb_ycc_tab;
|
||||
register _JSAMPROW inptr;
|
||||
register _JSAMPROW outptr0, outptr1, outptr2;
|
||||
register JDIMENSION col;
|
||||
JDIMENSION num_cols = cinfo->image_width;
|
||||
|
||||
while (--num_rows >= 0) {
|
||||
inptr = *input_buf++;
|
||||
outptr0 = output_buf[0][output_row];
|
||||
outptr1 = output_buf[1][output_row];
|
||||
outptr2 = output_buf[2][output_row];
|
||||
output_row++;
|
||||
for (col = 0; col < num_cols; col++) {
|
||||
r = RANGE_LIMIT(inptr[RGB_RED]);
|
||||
g = RANGE_LIMIT(inptr[RGB_GREEN]);
|
||||
b = RANGE_LIMIT(inptr[RGB_BLUE]);
|
||||
inptr += RGB_PIXELSIZE;
|
||||
/* If the inputs are 0.._MAXJSAMPLE, the outputs of these equations
|
||||
* must be too; we do not need an explicit range-limiting operation.
|
||||
* Hence the value being shifted is never negative, and we don't
|
||||
* need the general RIGHT_SHIFT macro.
|
||||
*/
|
||||
/* Y */
|
||||
outptr0[col] = (_JSAMPLE)((ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] +
|
||||
ctab[b + B_Y_OFF]) >> SCALEBITS);
|
||||
/* Cb */
|
||||
outptr1[col] = (_JSAMPLE)((ctab[r + R_CB_OFF] + ctab[g + G_CB_OFF] +
|
||||
ctab[b + B_CB_OFF]) >> SCALEBITS);
|
||||
/* Cr */
|
||||
outptr2[col] = (_JSAMPLE)((ctab[r + R_CR_OFF] + ctab[g + G_CR_OFF] +
|
||||
ctab[b + B_CR_OFF]) >> SCALEBITS);
|
||||
}
|
||||
}
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**************** Cases other than RGB -> YCbCr **************/
|
||||
|
||||
|
||||
/*
|
||||
* Convert some rows of samples to the JPEG colorspace.
|
||||
* This version handles RGB->grayscale conversion, which is the same
|
||||
* as the RGB->Y portion of RGB->YCbCr.
|
||||
* We assume rgb_ycc_start has been called (we only use the Y tables).
|
||||
*/
|
||||
|
||||
INLINE
|
||||
LOCAL(void)
|
||||
rgb_gray_convert_internal(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row,
|
||||
int num_rows)
|
||||
{
|
||||
#if BITS_IN_JSAMPLE != 16
|
||||
my_cconvert_ptr cconvert = (my_cconvert_ptr)cinfo->cconvert;
|
||||
register int r, g, b;
|
||||
register JLONG *ctab = cconvert->rgb_ycc_tab;
|
||||
register _JSAMPROW inptr;
|
||||
register _JSAMPROW outptr;
|
||||
register JDIMENSION col;
|
||||
JDIMENSION num_cols = cinfo->image_width;
|
||||
|
||||
while (--num_rows >= 0) {
|
||||
inptr = *input_buf++;
|
||||
outptr = output_buf[0][output_row];
|
||||
output_row++;
|
||||
for (col = 0; col < num_cols; col++) {
|
||||
r = RANGE_LIMIT(inptr[RGB_RED]);
|
||||
g = RANGE_LIMIT(inptr[RGB_GREEN]);
|
||||
b = RANGE_LIMIT(inptr[RGB_BLUE]);
|
||||
inptr += RGB_PIXELSIZE;
|
||||
/* Y */
|
||||
outptr[col] = (_JSAMPLE)((ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] +
|
||||
ctab[b + B_Y_OFF]) >> SCALEBITS);
|
||||
}
|
||||
}
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert some rows of samples to the JPEG colorspace.
|
||||
* This version handles extended RGB->plain RGB conversion
|
||||
*/
|
||||
|
||||
INLINE
|
||||
LOCAL(void)
|
||||
rgb_rgb_convert_internal(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row,
|
||||
int num_rows)
|
||||
{
|
||||
register _JSAMPROW inptr;
|
||||
register _JSAMPROW outptr0, outptr1, outptr2;
|
||||
register JDIMENSION col;
|
||||
JDIMENSION num_cols = cinfo->image_width;
|
||||
|
||||
while (--num_rows >= 0) {
|
||||
inptr = *input_buf++;
|
||||
outptr0 = output_buf[0][output_row];
|
||||
outptr1 = output_buf[1][output_row];
|
||||
outptr2 = output_buf[2][output_row];
|
||||
output_row++;
|
||||
for (col = 0; col < num_cols; col++) {
|
||||
outptr0[col] = inptr[RGB_RED];
|
||||
outptr1[col] = inptr[RGB_GREEN];
|
||||
outptr2[col] = inptr[RGB_BLUE];
|
||||
inptr += RGB_PIXELSIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
+732
@@ -0,0 +1,732 @@
|
||||
/*
|
||||
* jccolor.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1991-1996, Thomas G. Lane.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
|
||||
* Copyright (C) 2009-2012, 2015, 2022, D. R. Commander.
|
||||
* Copyright (C) 2014, MIPS Technologies, Inc., California.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains input colorspace conversion routines.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jsimd.h"
|
||||
#include "jsamplecomp.h"
|
||||
|
||||
|
||||
#if BITS_IN_JSAMPLE != 16 || defined(C_LOSSLESS_SUPPORTED)
|
||||
|
||||
/* Private subobject */
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_color_converter pub; /* public fields */
|
||||
|
||||
#if BITS_IN_JSAMPLE != 16
|
||||
/* Private state for RGB->YCC conversion */
|
||||
JLONG *rgb_ycc_tab; /* => table for RGB to YCbCr conversion */
|
||||
#endif
|
||||
} my_color_converter;
|
||||
|
||||
typedef my_color_converter *my_cconvert_ptr;
|
||||
|
||||
|
||||
/**************** RGB -> YCbCr conversion: most common case **************/
|
||||
|
||||
/*
|
||||
* YCbCr is defined per CCIR 601-1, except that Cb and Cr are
|
||||
* normalized to the range 0.._MAXJSAMPLE rather than -0.5 .. 0.5.
|
||||
* The conversion equations to be implemented are therefore
|
||||
* Y = 0.29900 * R + 0.58700 * G + 0.11400 * B
|
||||
* Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + _CENTERJSAMPLE
|
||||
* Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + _CENTERJSAMPLE
|
||||
* (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.)
|
||||
* Note: older versions of the IJG code used a zero offset of _MAXJSAMPLE/2,
|
||||
* rather than _CENTERJSAMPLE, for Cb and Cr. This gave equal positive and
|
||||
* negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0)
|
||||
* were not represented exactly. Now we sacrifice exact representation of
|
||||
* maximum red and maximum blue in order to get exact grayscales.
|
||||
*
|
||||
* To avoid floating-point arithmetic, we represent the fractional constants
|
||||
* as integers scaled up by 2^16 (about 4 digits precision); we have to divide
|
||||
* the products by 2^16, with appropriate rounding, to get the correct answer.
|
||||
*
|
||||
* For even more speed, we avoid doing any multiplications in the inner loop
|
||||
* by precalculating the constants times R,G,B for all possible values.
|
||||
* For 8-bit samples this is very reasonable (only 256 entries per table);
|
||||
* for 12-bit samples it is still acceptable. It's not very reasonable for
|
||||
* 16-bit samples, but if you want lossless storage you shouldn't be changing
|
||||
* colorspace anyway.
|
||||
* The _CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included
|
||||
* in the tables to save adding them separately in the inner loop.
|
||||
*/
|
||||
|
||||
#define SCALEBITS 16 /* speediest right-shift on some machines */
|
||||
#define CBCR_OFFSET ((JLONG)_CENTERJSAMPLE << SCALEBITS)
|
||||
#define ONE_HALF ((JLONG)1 << (SCALEBITS - 1))
|
||||
#define FIX(x) ((JLONG)((x) * (1L << SCALEBITS) + 0.5))
|
||||
|
||||
/* We allocate one big table and divide it up into eight parts, instead of
|
||||
* doing eight alloc_small requests. This lets us use a single table base
|
||||
* address, which can be held in a register in the inner loops on many
|
||||
* machines (more than can hold all eight addresses, anyway).
|
||||
*/
|
||||
|
||||
#define R_Y_OFF 0 /* offset to R => Y section */
|
||||
#define G_Y_OFF (1 * (_MAXJSAMPLE + 1)) /* offset to G => Y section */
|
||||
#define B_Y_OFF (2 * (_MAXJSAMPLE + 1)) /* etc. */
|
||||
#define R_CB_OFF (3 * (_MAXJSAMPLE + 1))
|
||||
#define G_CB_OFF (4 * (_MAXJSAMPLE + 1))
|
||||
#define B_CB_OFF (5 * (_MAXJSAMPLE + 1))
|
||||
#define R_CR_OFF B_CB_OFF /* B=>Cb, R=>Cr are the same */
|
||||
#define G_CR_OFF (6 * (_MAXJSAMPLE + 1))
|
||||
#define B_CR_OFF (7 * (_MAXJSAMPLE + 1))
|
||||
#define TABLE_SIZE (8 * (_MAXJSAMPLE + 1))
|
||||
|
||||
/* 12-bit samples use a 16-bit data type, so it is possible to pass
|
||||
* out-of-range sample values (< 0 or > 4095) to jpeg_write_scanlines().
|
||||
* Thus, we mask the incoming 12-bit samples to guard against overrunning
|
||||
* or underrunning the conversion tables.
|
||||
*/
|
||||
|
||||
#if BITS_IN_JSAMPLE == 12
|
||||
#define RANGE_LIMIT(value) ((value) & 0xFFF)
|
||||
#else
|
||||
#define RANGE_LIMIT(value) (value)
|
||||
#endif
|
||||
|
||||
|
||||
/* Include inline routines for colorspace extensions */
|
||||
|
||||
#include "jccolext.c"
|
||||
#undef RGB_RED
|
||||
#undef RGB_GREEN
|
||||
#undef RGB_BLUE
|
||||
#undef RGB_PIXELSIZE
|
||||
|
||||
#define RGB_RED EXT_RGB_RED
|
||||
#define RGB_GREEN EXT_RGB_GREEN
|
||||
#define RGB_BLUE EXT_RGB_BLUE
|
||||
#define RGB_PIXELSIZE EXT_RGB_PIXELSIZE
|
||||
#define rgb_ycc_convert_internal extrgb_ycc_convert_internal
|
||||
#define rgb_gray_convert_internal extrgb_gray_convert_internal
|
||||
#define rgb_rgb_convert_internal extrgb_rgb_convert_internal
|
||||
#include "jccolext.c"
|
||||
#undef RGB_RED
|
||||
#undef RGB_GREEN
|
||||
#undef RGB_BLUE
|
||||
#undef RGB_PIXELSIZE
|
||||
#undef rgb_ycc_convert_internal
|
||||
#undef rgb_gray_convert_internal
|
||||
#undef rgb_rgb_convert_internal
|
||||
|
||||
#define RGB_RED EXT_RGBX_RED
|
||||
#define RGB_GREEN EXT_RGBX_GREEN
|
||||
#define RGB_BLUE EXT_RGBX_BLUE
|
||||
#define RGB_PIXELSIZE EXT_RGBX_PIXELSIZE
|
||||
#define rgb_ycc_convert_internal extrgbx_ycc_convert_internal
|
||||
#define rgb_gray_convert_internal extrgbx_gray_convert_internal
|
||||
#define rgb_rgb_convert_internal extrgbx_rgb_convert_internal
|
||||
#include "jccolext.c"
|
||||
#undef RGB_RED
|
||||
#undef RGB_GREEN
|
||||
#undef RGB_BLUE
|
||||
#undef RGB_PIXELSIZE
|
||||
#undef rgb_ycc_convert_internal
|
||||
#undef rgb_gray_convert_internal
|
||||
#undef rgb_rgb_convert_internal
|
||||
|
||||
#define RGB_RED EXT_BGR_RED
|
||||
#define RGB_GREEN EXT_BGR_GREEN
|
||||
#define RGB_BLUE EXT_BGR_BLUE
|
||||
#define RGB_PIXELSIZE EXT_BGR_PIXELSIZE
|
||||
#define rgb_ycc_convert_internal extbgr_ycc_convert_internal
|
||||
#define rgb_gray_convert_internal extbgr_gray_convert_internal
|
||||
#define rgb_rgb_convert_internal extbgr_rgb_convert_internal
|
||||
#include "jccolext.c"
|
||||
#undef RGB_RED
|
||||
#undef RGB_GREEN
|
||||
#undef RGB_BLUE
|
||||
#undef RGB_PIXELSIZE
|
||||
#undef rgb_ycc_convert_internal
|
||||
#undef rgb_gray_convert_internal
|
||||
#undef rgb_rgb_convert_internal
|
||||
|
||||
#define RGB_RED EXT_BGRX_RED
|
||||
#define RGB_GREEN EXT_BGRX_GREEN
|
||||
#define RGB_BLUE EXT_BGRX_BLUE
|
||||
#define RGB_PIXELSIZE EXT_BGRX_PIXELSIZE
|
||||
#define rgb_ycc_convert_internal extbgrx_ycc_convert_internal
|
||||
#define rgb_gray_convert_internal extbgrx_gray_convert_internal
|
||||
#define rgb_rgb_convert_internal extbgrx_rgb_convert_internal
|
||||
#include "jccolext.c"
|
||||
#undef RGB_RED
|
||||
#undef RGB_GREEN
|
||||
#undef RGB_BLUE
|
||||
#undef RGB_PIXELSIZE
|
||||
#undef rgb_ycc_convert_internal
|
||||
#undef rgb_gray_convert_internal
|
||||
#undef rgb_rgb_convert_internal
|
||||
|
||||
#define RGB_RED EXT_XBGR_RED
|
||||
#define RGB_GREEN EXT_XBGR_GREEN
|
||||
#define RGB_BLUE EXT_XBGR_BLUE
|
||||
#define RGB_PIXELSIZE EXT_XBGR_PIXELSIZE
|
||||
#define rgb_ycc_convert_internal extxbgr_ycc_convert_internal
|
||||
#define rgb_gray_convert_internal extxbgr_gray_convert_internal
|
||||
#define rgb_rgb_convert_internal extxbgr_rgb_convert_internal
|
||||
#include "jccolext.c"
|
||||
#undef RGB_RED
|
||||
#undef RGB_GREEN
|
||||
#undef RGB_BLUE
|
||||
#undef RGB_PIXELSIZE
|
||||
#undef rgb_ycc_convert_internal
|
||||
#undef rgb_gray_convert_internal
|
||||
#undef rgb_rgb_convert_internal
|
||||
|
||||
#define RGB_RED EXT_XRGB_RED
|
||||
#define RGB_GREEN EXT_XRGB_GREEN
|
||||
#define RGB_BLUE EXT_XRGB_BLUE
|
||||
#define RGB_PIXELSIZE EXT_XRGB_PIXELSIZE
|
||||
#define rgb_ycc_convert_internal extxrgb_ycc_convert_internal
|
||||
#define rgb_gray_convert_internal extxrgb_gray_convert_internal
|
||||
#define rgb_rgb_convert_internal extxrgb_rgb_convert_internal
|
||||
#include "jccolext.c"
|
||||
#undef RGB_RED
|
||||
#undef RGB_GREEN
|
||||
#undef RGB_BLUE
|
||||
#undef RGB_PIXELSIZE
|
||||
#undef rgb_ycc_convert_internal
|
||||
#undef rgb_gray_convert_internal
|
||||
#undef rgb_rgb_convert_internal
|
||||
|
||||
|
||||
/*
|
||||
* Initialize for RGB->YCC colorspace conversion.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
rgb_ycc_start(j_compress_ptr cinfo)
|
||||
{
|
||||
#if BITS_IN_JSAMPLE != 16
|
||||
my_cconvert_ptr cconvert = (my_cconvert_ptr)cinfo->cconvert;
|
||||
JLONG *rgb_ycc_tab;
|
||||
JLONG i;
|
||||
|
||||
/* Allocate and fill in the conversion tables. */
|
||||
cconvert->rgb_ycc_tab = rgb_ycc_tab = (JLONG *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
(TABLE_SIZE * sizeof(JLONG)));
|
||||
|
||||
for (i = 0; i <= _MAXJSAMPLE; i++) {
|
||||
rgb_ycc_tab[i + R_Y_OFF] = FIX(0.29900) * i;
|
||||
rgb_ycc_tab[i + G_Y_OFF] = FIX(0.58700) * i;
|
||||
rgb_ycc_tab[i + B_Y_OFF] = FIX(0.11400) * i + ONE_HALF;
|
||||
rgb_ycc_tab[i + R_CB_OFF] = (-FIX(0.16874)) * i;
|
||||
rgb_ycc_tab[i + G_CB_OFF] = (-FIX(0.33126)) * i;
|
||||
/* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr.
|
||||
* This ensures that the maximum output will round to _MAXJSAMPLE
|
||||
* not _MAXJSAMPLE+1, and thus that we don't have to range-limit.
|
||||
*/
|
||||
rgb_ycc_tab[i + B_CB_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF - 1;
|
||||
/* B=>Cb and R=>Cr tables are the same
|
||||
rgb_ycc_tab[i + R_CR_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF - 1;
|
||||
*/
|
||||
rgb_ycc_tab[i + G_CR_OFF] = (-FIX(0.41869)) * i;
|
||||
rgb_ycc_tab[i + B_CR_OFF] = (-FIX(0.08131)) * i;
|
||||
}
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert some rows of samples to the JPEG colorspace.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
rgb_ycc_convert(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row, int num_rows)
|
||||
{
|
||||
switch (cinfo->in_color_space) {
|
||||
case JCS_EXT_RGB:
|
||||
extrgb_ycc_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_RGBX:
|
||||
case JCS_EXT_RGBA:
|
||||
extrgbx_ycc_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_BGR:
|
||||
extbgr_ycc_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_BGRX:
|
||||
case JCS_EXT_BGRA:
|
||||
extbgrx_ycc_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_XBGR:
|
||||
case JCS_EXT_ABGR:
|
||||
extxbgr_ycc_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_XRGB:
|
||||
case JCS_EXT_ARGB:
|
||||
extxrgb_ycc_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
default:
|
||||
rgb_ycc_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**************** Cases other than RGB -> YCbCr **************/
|
||||
|
||||
|
||||
/*
|
||||
* Convert some rows of samples to the JPEG colorspace.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
rgb_gray_convert(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row, int num_rows)
|
||||
{
|
||||
switch (cinfo->in_color_space) {
|
||||
case JCS_EXT_RGB:
|
||||
extrgb_gray_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_RGBX:
|
||||
case JCS_EXT_RGBA:
|
||||
extrgbx_gray_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_BGR:
|
||||
extbgr_gray_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_BGRX:
|
||||
case JCS_EXT_BGRA:
|
||||
extbgrx_gray_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_XBGR:
|
||||
case JCS_EXT_ABGR:
|
||||
extxbgr_gray_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_XRGB:
|
||||
case JCS_EXT_ARGB:
|
||||
extxrgb_gray_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
default:
|
||||
rgb_gray_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Extended RGB to plain RGB conversion
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
rgb_rgb_convert(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row, int num_rows)
|
||||
{
|
||||
switch (cinfo->in_color_space) {
|
||||
case JCS_EXT_RGB:
|
||||
extrgb_rgb_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_RGBX:
|
||||
case JCS_EXT_RGBA:
|
||||
extrgbx_rgb_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_BGR:
|
||||
extbgr_rgb_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_BGRX:
|
||||
case JCS_EXT_BGRA:
|
||||
extbgrx_rgb_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_XBGR:
|
||||
case JCS_EXT_ABGR:
|
||||
extxbgr_rgb_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
case JCS_EXT_XRGB:
|
||||
case JCS_EXT_ARGB:
|
||||
extxrgb_rgb_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
default:
|
||||
rgb_rgb_convert_internal(cinfo, input_buf, output_buf, output_row,
|
||||
num_rows);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert some rows of samples to the JPEG colorspace.
|
||||
* This version handles Adobe-style CMYK->YCCK conversion,
|
||||
* where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same
|
||||
* conversion as above, while passing K (black) unchanged.
|
||||
* We assume rgb_ycc_start has been called.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
cmyk_ycck_convert(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row, int num_rows)
|
||||
{
|
||||
#if BITS_IN_JSAMPLE != 16
|
||||
my_cconvert_ptr cconvert = (my_cconvert_ptr)cinfo->cconvert;
|
||||
register int r, g, b;
|
||||
register JLONG *ctab = cconvert->rgb_ycc_tab;
|
||||
register _JSAMPROW inptr;
|
||||
register _JSAMPROW outptr0, outptr1, outptr2, outptr3;
|
||||
register JDIMENSION col;
|
||||
JDIMENSION num_cols = cinfo->image_width;
|
||||
|
||||
while (--num_rows >= 0) {
|
||||
inptr = *input_buf++;
|
||||
outptr0 = output_buf[0][output_row];
|
||||
outptr1 = output_buf[1][output_row];
|
||||
outptr2 = output_buf[2][output_row];
|
||||
outptr3 = output_buf[3][output_row];
|
||||
output_row++;
|
||||
for (col = 0; col < num_cols; col++) {
|
||||
r = _MAXJSAMPLE - RANGE_LIMIT(inptr[0]);
|
||||
g = _MAXJSAMPLE - RANGE_LIMIT(inptr[1]);
|
||||
b = _MAXJSAMPLE - RANGE_LIMIT(inptr[2]);
|
||||
/* K passes through as-is */
|
||||
outptr3[col] = inptr[3];
|
||||
inptr += 4;
|
||||
/* If the inputs are 0.._MAXJSAMPLE, the outputs of these equations
|
||||
* must be too; we do not need an explicit range-limiting operation.
|
||||
* Hence the value being shifted is never negative, and we don't
|
||||
* need the general RIGHT_SHIFT macro.
|
||||
*/
|
||||
/* Y */
|
||||
outptr0[col] = (_JSAMPLE)((ctab[r + R_Y_OFF] + ctab[g + G_Y_OFF] +
|
||||
ctab[b + B_Y_OFF]) >> SCALEBITS);
|
||||
/* Cb */
|
||||
outptr1[col] = (_JSAMPLE)((ctab[r + R_CB_OFF] + ctab[g + G_CB_OFF] +
|
||||
ctab[b + B_CB_OFF]) >> SCALEBITS);
|
||||
/* Cr */
|
||||
outptr2[col] = (_JSAMPLE)((ctab[r + R_CR_OFF] + ctab[g + G_CR_OFF] +
|
||||
ctab[b + B_CR_OFF]) >> SCALEBITS);
|
||||
}
|
||||
}
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert some rows of samples to the JPEG colorspace.
|
||||
* This version handles grayscale output with no conversion.
|
||||
* The source can be either plain grayscale or YCbCr (since Y == gray).
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
grayscale_convert(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row, int num_rows)
|
||||
{
|
||||
register _JSAMPROW inptr;
|
||||
register _JSAMPROW outptr;
|
||||
register JDIMENSION col;
|
||||
JDIMENSION num_cols = cinfo->image_width;
|
||||
int instride = cinfo->input_components;
|
||||
|
||||
while (--num_rows >= 0) {
|
||||
inptr = *input_buf++;
|
||||
outptr = output_buf[0][output_row];
|
||||
output_row++;
|
||||
for (col = 0; col < num_cols; col++) {
|
||||
outptr[col] = inptr[0];
|
||||
inptr += instride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert some rows of samples to the JPEG colorspace.
|
||||
* This version handles multi-component colorspaces without conversion.
|
||||
* We assume input_components == num_components.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
null_convert(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
_JSAMPIMAGE output_buf, JDIMENSION output_row, int num_rows)
|
||||
{
|
||||
register _JSAMPROW inptr;
|
||||
register _JSAMPROW outptr, outptr0, outptr1, outptr2, outptr3;
|
||||
register JDIMENSION col;
|
||||
register int ci;
|
||||
int nc = cinfo->num_components;
|
||||
JDIMENSION num_cols = cinfo->image_width;
|
||||
|
||||
if (nc == 3) {
|
||||
while (--num_rows >= 0) {
|
||||
inptr = *input_buf++;
|
||||
outptr0 = output_buf[0][output_row];
|
||||
outptr1 = output_buf[1][output_row];
|
||||
outptr2 = output_buf[2][output_row];
|
||||
output_row++;
|
||||
for (col = 0; col < num_cols; col++) {
|
||||
outptr0[col] = *inptr++;
|
||||
outptr1[col] = *inptr++;
|
||||
outptr2[col] = *inptr++;
|
||||
}
|
||||
}
|
||||
} else if (nc == 4) {
|
||||
while (--num_rows >= 0) {
|
||||
inptr = *input_buf++;
|
||||
outptr0 = output_buf[0][output_row];
|
||||
outptr1 = output_buf[1][output_row];
|
||||
outptr2 = output_buf[2][output_row];
|
||||
outptr3 = output_buf[3][output_row];
|
||||
output_row++;
|
||||
for (col = 0; col < num_cols; col++) {
|
||||
outptr0[col] = *inptr++;
|
||||
outptr1[col] = *inptr++;
|
||||
outptr2[col] = *inptr++;
|
||||
outptr3[col] = *inptr++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (--num_rows >= 0) {
|
||||
/* It seems fastest to make a separate pass for each component. */
|
||||
for (ci = 0; ci < nc; ci++) {
|
||||
inptr = *input_buf;
|
||||
outptr = output_buf[ci][output_row];
|
||||
for (col = 0; col < num_cols; col++) {
|
||||
outptr[col] = inptr[ci];
|
||||
inptr += nc;
|
||||
}
|
||||
}
|
||||
input_buf++;
|
||||
output_row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Empty method for start_pass.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
null_method(j_compress_ptr cinfo)
|
||||
{
|
||||
/* no work needed */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Module initialization routine for input colorspace conversion.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
_jinit_color_converter(j_compress_ptr cinfo)
|
||||
{
|
||||
my_cconvert_ptr cconvert;
|
||||
|
||||
if (cinfo->data_precision != BITS_IN_JSAMPLE)
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
|
||||
cconvert = (my_cconvert_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(my_color_converter));
|
||||
cinfo->cconvert = (struct jpeg_color_converter *)cconvert;
|
||||
/* set start_pass to null method until we find out differently */
|
||||
cconvert->pub.start_pass = null_method;
|
||||
|
||||
/* Make sure input_components agrees with in_color_space */
|
||||
switch (cinfo->in_color_space) {
|
||||
case JCS_GRAYSCALE:
|
||||
if (cinfo->input_components != 1)
|
||||
ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
|
||||
break;
|
||||
|
||||
case JCS_RGB:
|
||||
case JCS_EXT_RGB:
|
||||
case JCS_EXT_RGBX:
|
||||
case JCS_EXT_BGR:
|
||||
case JCS_EXT_BGRX:
|
||||
case JCS_EXT_XBGR:
|
||||
case JCS_EXT_XRGB:
|
||||
case JCS_EXT_RGBA:
|
||||
case JCS_EXT_BGRA:
|
||||
case JCS_EXT_ABGR:
|
||||
case JCS_EXT_ARGB:
|
||||
if (cinfo->input_components != rgb_pixelsize[cinfo->in_color_space])
|
||||
ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
|
||||
break;
|
||||
|
||||
case JCS_YCbCr:
|
||||
if (cinfo->input_components != 3)
|
||||
ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
|
||||
break;
|
||||
|
||||
case JCS_CMYK:
|
||||
case JCS_YCCK:
|
||||
if (cinfo->input_components != 4)
|
||||
ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
|
||||
break;
|
||||
|
||||
default: /* JCS_UNKNOWN can be anything */
|
||||
if (cinfo->input_components < 1)
|
||||
ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check num_components, set conversion method based on requested space.
|
||||
* NOTE: We do not allow any lossy color conversion algorithms in lossless
|
||||
* mode.
|
||||
*/
|
||||
switch (cinfo->jpeg_color_space) {
|
||||
case JCS_GRAYSCALE:
|
||||
if (cinfo->master->lossless &&
|
||||
cinfo->in_color_space != cinfo->jpeg_color_space)
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
if (cinfo->num_components != 1)
|
||||
ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
|
||||
if (cinfo->in_color_space == JCS_GRAYSCALE)
|
||||
cconvert->pub._color_convert = grayscale_convert;
|
||||
else if (IsExtRGB(cinfo->in_color_space)) {
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_rgb_gray())
|
||||
cconvert->pub._color_convert = jsimd_rgb_gray_convert;
|
||||
else
|
||||
#endif
|
||||
{
|
||||
cconvert->pub.start_pass = rgb_ycc_start;
|
||||
cconvert->pub._color_convert = rgb_gray_convert;
|
||||
}
|
||||
} else if (cinfo->in_color_space == JCS_YCbCr)
|
||||
cconvert->pub._color_convert = grayscale_convert;
|
||||
else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
break;
|
||||
|
||||
case JCS_RGB:
|
||||
if (cinfo->master->lossless && !IsExtRGB(cinfo->in_color_space))
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
if (cinfo->num_components != 3)
|
||||
ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
|
||||
if (rgb_red[cinfo->in_color_space] == 0 &&
|
||||
rgb_green[cinfo->in_color_space] == 1 &&
|
||||
rgb_blue[cinfo->in_color_space] == 2 &&
|
||||
rgb_pixelsize[cinfo->in_color_space] == 3) {
|
||||
#if defined(WITH_SIMD) && defined(__mips__)
|
||||
if (jsimd_c_can_null_convert())
|
||||
cconvert->pub._color_convert = jsimd_c_null_convert;
|
||||
else
|
||||
#endif
|
||||
cconvert->pub._color_convert = null_convert;
|
||||
} else if (IsExtRGB(cinfo->in_color_space))
|
||||
cconvert->pub._color_convert = rgb_rgb_convert;
|
||||
else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
break;
|
||||
|
||||
case JCS_YCbCr:
|
||||
if (cinfo->master->lossless &&
|
||||
cinfo->in_color_space != cinfo->jpeg_color_space)
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
if (cinfo->num_components != 3)
|
||||
ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
|
||||
if (IsExtRGB(cinfo->in_color_space)) {
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_rgb_ycc())
|
||||
cconvert->pub._color_convert = jsimd_rgb_ycc_convert;
|
||||
else
|
||||
#endif
|
||||
{
|
||||
cconvert->pub.start_pass = rgb_ycc_start;
|
||||
cconvert->pub._color_convert = rgb_ycc_convert;
|
||||
}
|
||||
} else if (cinfo->in_color_space == JCS_YCbCr) {
|
||||
#if defined(WITH_SIMD) && defined(__mips__)
|
||||
if (jsimd_c_can_null_convert())
|
||||
cconvert->pub._color_convert = jsimd_c_null_convert;
|
||||
else
|
||||
#endif
|
||||
cconvert->pub._color_convert = null_convert;
|
||||
} else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
break;
|
||||
|
||||
case JCS_CMYK:
|
||||
if (cinfo->master->lossless &&
|
||||
cinfo->in_color_space != cinfo->jpeg_color_space)
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
if (cinfo->num_components != 4)
|
||||
ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
|
||||
if (cinfo->in_color_space == JCS_CMYK) {
|
||||
#if defined(WITH_SIMD) && defined(__mips__)
|
||||
if (jsimd_c_can_null_convert())
|
||||
cconvert->pub._color_convert = jsimd_c_null_convert;
|
||||
else
|
||||
#endif
|
||||
cconvert->pub._color_convert = null_convert;
|
||||
} else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
break;
|
||||
|
||||
case JCS_YCCK:
|
||||
if (cinfo->master->lossless &&
|
||||
cinfo->in_color_space != cinfo->jpeg_color_space)
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
if (cinfo->num_components != 4)
|
||||
ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
|
||||
if (cinfo->in_color_space == JCS_CMYK) {
|
||||
cconvert->pub.start_pass = rgb_ycc_start;
|
||||
cconvert->pub._color_convert = cmyk_ycck_convert;
|
||||
} else if (cinfo->in_color_space == JCS_YCCK) {
|
||||
#if defined(WITH_SIMD) && defined(__mips__)
|
||||
if (jsimd_c_can_null_convert())
|
||||
cconvert->pub._color_convert = jsimd_c_null_convert;
|
||||
else
|
||||
#endif
|
||||
cconvert->pub._color_convert = null_convert;
|
||||
} else
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
break;
|
||||
|
||||
default: /* allow null conversion of JCS_UNKNOWN */
|
||||
if (cinfo->jpeg_color_space != cinfo->in_color_space ||
|
||||
cinfo->num_components != cinfo->input_components)
|
||||
ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
|
||||
#if defined(WITH_SIMD) && defined(__mips__)
|
||||
if (jsimd_c_can_null_convert())
|
||||
cconvert->pub._color_convert = jsimd_c_null_convert;
|
||||
else
|
||||
#endif
|
||||
cconvert->pub._color_convert = null_convert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* BITS_IN_JSAMPLE != 16 || defined(C_LOSSLESS_SUPPORTED) */
|
||||
+748
@@ -0,0 +1,748 @@
|
||||
/*
|
||||
* jcdctmgr.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1994-1996, Thomas G. Lane.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 1999-2006, MIYASAKA Masaru.
|
||||
* Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
|
||||
* Copyright (C) 2011, 2014-2015, 2022, 2024, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains the forward-DCT management logic.
|
||||
* This code selects a particular DCT implementation to be used,
|
||||
* and it performs related housekeeping chores including coefficient
|
||||
* quantization.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jdct.h" /* Private declarations for DCT subsystem */
|
||||
#include "jsimddct.h"
|
||||
|
||||
|
||||
/* Private subobject for this module */
|
||||
|
||||
typedef void (*forward_DCT_method_ptr) (DCTELEM *data);
|
||||
typedef void (*float_DCT_method_ptr) (FAST_FLOAT *data);
|
||||
|
||||
typedef void (*convsamp_method_ptr) (_JSAMPARRAY sample_data,
|
||||
JDIMENSION start_col,
|
||||
DCTELEM *workspace);
|
||||
typedef void (*float_convsamp_method_ptr) (_JSAMPARRAY sample_data,
|
||||
JDIMENSION start_col,
|
||||
FAST_FLOAT *workspace);
|
||||
|
||||
typedef void (*quantize_method_ptr) (JCOEFPTR coef_block, DCTELEM *divisors,
|
||||
DCTELEM *workspace);
|
||||
typedef void (*float_quantize_method_ptr) (JCOEFPTR coef_block,
|
||||
FAST_FLOAT *divisors,
|
||||
FAST_FLOAT *workspace);
|
||||
|
||||
METHODDEF(void) quantize(JCOEFPTR, DCTELEM *, DCTELEM *);
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_forward_dct pub; /* public fields */
|
||||
|
||||
/* Pointer to the DCT routine actually in use */
|
||||
forward_DCT_method_ptr dct;
|
||||
convsamp_method_ptr convsamp;
|
||||
quantize_method_ptr quantize;
|
||||
|
||||
/* The actual post-DCT divisors --- not identical to the quant table
|
||||
* entries, because of scaling (especially for an unnormalized DCT).
|
||||
* Each table is given in normal array order.
|
||||
*/
|
||||
DCTELEM *divisors[NUM_QUANT_TBLS];
|
||||
|
||||
/* work area for FDCT subroutine */
|
||||
DCTELEM *workspace;
|
||||
|
||||
#ifdef DCT_FLOAT_SUPPORTED
|
||||
/* Same as above for the floating-point case. */
|
||||
float_DCT_method_ptr float_dct;
|
||||
float_convsamp_method_ptr float_convsamp;
|
||||
float_quantize_method_ptr float_quantize;
|
||||
FAST_FLOAT *float_divisors[NUM_QUANT_TBLS];
|
||||
FAST_FLOAT *float_workspace;
|
||||
#endif
|
||||
} my_fdct_controller;
|
||||
|
||||
typedef my_fdct_controller *my_fdct_ptr;
|
||||
|
||||
|
||||
#if BITS_IN_JSAMPLE == 8
|
||||
|
||||
/*
|
||||
* Find the highest bit in an integer through binary search.
|
||||
*/
|
||||
|
||||
LOCAL(int)
|
||||
flss(UINT16 val)
|
||||
{
|
||||
int bit;
|
||||
|
||||
bit = 16;
|
||||
|
||||
if (!val)
|
||||
return 0;
|
||||
|
||||
if (!(val & 0xff00)) {
|
||||
bit -= 8;
|
||||
val <<= 8;
|
||||
}
|
||||
if (!(val & 0xf000)) {
|
||||
bit -= 4;
|
||||
val <<= 4;
|
||||
}
|
||||
if (!(val & 0xc000)) {
|
||||
bit -= 2;
|
||||
val <<= 2;
|
||||
}
|
||||
if (!(val & 0x8000)) {
|
||||
bit -= 1;
|
||||
val <<= 1;
|
||||
}
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Compute values to do a division using reciprocal.
|
||||
*
|
||||
* This implementation is based on an algorithm described in
|
||||
* "Optimizing subroutines in assembly language:
|
||||
* An optimization guide for x86 platforms" (https://agner.org/optimize).
|
||||
* More information about the basic algorithm can be found in
|
||||
* the paper "Integer Division Using Reciprocals" by Robert Alverson.
|
||||
*
|
||||
* The basic idea is to replace x/d by x * d^-1. In order to store
|
||||
* d^-1 with enough precision we shift it left a few places. It turns
|
||||
* out that this algoright gives just enough precision, and also fits
|
||||
* into DCTELEM:
|
||||
*
|
||||
* b = (the number of significant bits in divisor) - 1
|
||||
* r = (word size) + b
|
||||
* f = 2^r / divisor
|
||||
*
|
||||
* f will not be an integer for most cases, so we need to compensate
|
||||
* for the rounding error introduced:
|
||||
*
|
||||
* no fractional part:
|
||||
*
|
||||
* result = input >> r
|
||||
*
|
||||
* fractional part of f < 0.5:
|
||||
*
|
||||
* round f down to nearest integer
|
||||
* result = ((input + 1) * f) >> r
|
||||
*
|
||||
* fractional part of f > 0.5:
|
||||
*
|
||||
* round f up to nearest integer
|
||||
* result = (input * f) >> r
|
||||
*
|
||||
* This is the original algorithm that gives truncated results. But we
|
||||
* want properly rounded results, so we replace "input" with
|
||||
* "input + divisor/2".
|
||||
*
|
||||
* In order to allow SIMD implementations we also tweak the values to
|
||||
* allow the same calculation to be made at all times:
|
||||
*
|
||||
* dctbl[0] = f rounded to nearest integer
|
||||
* dctbl[1] = divisor / 2 (+ 1 if fractional part of f < 0.5)
|
||||
* dctbl[2] = 1 << ((word size) * 2 - r)
|
||||
* dctbl[3] = r - (word size)
|
||||
*
|
||||
* dctbl[2] is for stupid instruction sets where the shift operation
|
||||
* isn't member wise (e.g. MMX).
|
||||
*
|
||||
* The reason dctbl[2] and dctbl[3] reduce the shift with (word size)
|
||||
* is that most SIMD implementations have a "multiply and store top
|
||||
* half" operation.
|
||||
*
|
||||
* Lastly, we store each of the values in their own table instead
|
||||
* of in a consecutive manner, yet again in order to allow SIMD
|
||||
* routines.
|
||||
*/
|
||||
|
||||
LOCAL(int)
|
||||
compute_reciprocal(UINT16 divisor, DCTELEM *dtbl)
|
||||
{
|
||||
UDCTELEM2 fq, fr;
|
||||
UDCTELEM c;
|
||||
int b, r;
|
||||
|
||||
if (divisor == 1) {
|
||||
/* divisor == 1 means unquantized, so these reciprocal/correction/shift
|
||||
* values will cause the C quantization algorithm to act like the
|
||||
* identity function. Since only the C quantization algorithm is used in
|
||||
* these cases, the scale value is irrelevant.
|
||||
*/
|
||||
dtbl[DCTSIZE2 * 0] = (DCTELEM)1; /* reciprocal */
|
||||
dtbl[DCTSIZE2 * 1] = (DCTELEM)0; /* correction */
|
||||
dtbl[DCTSIZE2 * 2] = (DCTELEM)1; /* scale */
|
||||
dtbl[DCTSIZE2 * 3] = -(DCTELEM)(sizeof(DCTELEM) * 8); /* shift */
|
||||
return 0;
|
||||
}
|
||||
|
||||
b = flss(divisor) - 1;
|
||||
r = sizeof(DCTELEM) * 8 + b;
|
||||
|
||||
fq = ((UDCTELEM2)1 << r) / divisor;
|
||||
fr = ((UDCTELEM2)1 << r) % divisor;
|
||||
|
||||
c = divisor / 2; /* for rounding */
|
||||
|
||||
if (fr == 0) { /* divisor is power of two */
|
||||
/* fq will be one bit too large to fit in DCTELEM, so adjust */
|
||||
fq >>= 1;
|
||||
r--;
|
||||
} else if (fr <= (divisor / 2U)) { /* fractional part is < 0.5 */
|
||||
c++;
|
||||
} else { /* fractional part is > 0.5 */
|
||||
fq++;
|
||||
}
|
||||
|
||||
dtbl[DCTSIZE2 * 0] = (DCTELEM)fq; /* reciprocal */
|
||||
dtbl[DCTSIZE2 * 1] = (DCTELEM)c; /* correction + roundfactor */
|
||||
#ifdef WITH_SIMD
|
||||
dtbl[DCTSIZE2 * 2] = (DCTELEM)(1 << (sizeof(DCTELEM) * 8 * 2 - r)); /* scale */
|
||||
#else
|
||||
dtbl[DCTSIZE2 * 2] = 1;
|
||||
#endif
|
||||
dtbl[DCTSIZE2 * 3] = (DCTELEM)r - sizeof(DCTELEM) * 8; /* shift */
|
||||
|
||||
if (r <= 16) return 0;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Initialize for a processing pass.
|
||||
* Verify that all referenced Q-tables are present, and set up
|
||||
* the divisor table for each one.
|
||||
* In the current implementation, DCT of all components is done during
|
||||
* the first pass, even if only some components will be output in the
|
||||
* first scan. Hence all components should be examined here.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
start_pass_fdctmgr(j_compress_ptr cinfo)
|
||||
{
|
||||
my_fdct_ptr fdct = (my_fdct_ptr)cinfo->fdct;
|
||||
int ci, qtblno, i;
|
||||
jpeg_component_info *compptr;
|
||||
JQUANT_TBL *qtbl;
|
||||
DCTELEM *dtbl;
|
||||
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
qtblno = compptr->quant_tbl_no;
|
||||
/* Make sure specified quantization table is present */
|
||||
if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS ||
|
||||
cinfo->quant_tbl_ptrs[qtblno] == NULL)
|
||||
ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno);
|
||||
qtbl = cinfo->quant_tbl_ptrs[qtblno];
|
||||
/* Compute divisors for this quant table */
|
||||
/* We may do this more than once for same table, but it's not a big deal */
|
||||
switch (cinfo->dct_method) {
|
||||
#ifdef DCT_ISLOW_SUPPORTED
|
||||
case JDCT_ISLOW:
|
||||
/* For LL&M IDCT method, divisors are equal to raw quantization
|
||||
* coefficients multiplied by 8 (to counteract scaling).
|
||||
*/
|
||||
if (fdct->divisors[qtblno] == NULL) {
|
||||
fdct->divisors[qtblno] = (DCTELEM *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
(DCTSIZE2 * 4) * sizeof(DCTELEM));
|
||||
}
|
||||
dtbl = fdct->divisors[qtblno];
|
||||
for (i = 0; i < DCTSIZE2; i++) {
|
||||
#if BITS_IN_JSAMPLE == 8
|
||||
#ifdef WITH_SIMD
|
||||
if (!compute_reciprocal(qtbl->quantval[i] << 3, &dtbl[i]) &&
|
||||
fdct->quantize == jsimd_quantize)
|
||||
fdct->quantize = quantize;
|
||||
#else
|
||||
compute_reciprocal(qtbl->quantval[i] << 3, &dtbl[i]);
|
||||
#endif
|
||||
#else
|
||||
dtbl[i] = ((DCTELEM)qtbl->quantval[i]) << 3;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef DCT_IFAST_SUPPORTED
|
||||
case JDCT_IFAST:
|
||||
{
|
||||
/* For AA&N IDCT method, divisors are equal to quantization
|
||||
* coefficients scaled by scalefactor[row]*scalefactor[col], where
|
||||
* scalefactor[0] = 1
|
||||
* scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
|
||||
* We apply a further scale factor of 8.
|
||||
*/
|
||||
#define CONST_BITS 14
|
||||
static const INT16 aanscales[DCTSIZE2] = {
|
||||
/* precomputed values scaled up by 14 bits */
|
||||
16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520,
|
||||
22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270,
|
||||
21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906,
|
||||
19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315,
|
||||
16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520,
|
||||
12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552,
|
||||
8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446,
|
||||
4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247
|
||||
};
|
||||
SHIFT_TEMPS
|
||||
|
||||
if (fdct->divisors[qtblno] == NULL) {
|
||||
fdct->divisors[qtblno] = (DCTELEM *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
(DCTSIZE2 * 4) * sizeof(DCTELEM));
|
||||
}
|
||||
dtbl = fdct->divisors[qtblno];
|
||||
for (i = 0; i < DCTSIZE2; i++) {
|
||||
#if BITS_IN_JSAMPLE == 8
|
||||
#ifdef WITH_SIMD
|
||||
if (!compute_reciprocal(
|
||||
DESCALE(MULTIPLY16V16((JLONG)qtbl->quantval[i],
|
||||
(JLONG)aanscales[i]),
|
||||
CONST_BITS - 3), &dtbl[i]) &&
|
||||
fdct->quantize == jsimd_quantize)
|
||||
fdct->quantize = quantize;
|
||||
#else
|
||||
compute_reciprocal(
|
||||
DESCALE(MULTIPLY16V16((JLONG)qtbl->quantval[i],
|
||||
(JLONG)aanscales[i]),
|
||||
CONST_BITS-3), &dtbl[i]);
|
||||
#endif
|
||||
#else
|
||||
dtbl[i] = (DCTELEM)
|
||||
DESCALE(MULTIPLY16V16((JLONG)qtbl->quantval[i],
|
||||
(JLONG)aanscales[i]),
|
||||
CONST_BITS - 3);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef DCT_FLOAT_SUPPORTED
|
||||
case JDCT_FLOAT:
|
||||
{
|
||||
/* For float AA&N IDCT method, divisors are equal to quantization
|
||||
* coefficients scaled by scalefactor[row]*scalefactor[col], where
|
||||
* scalefactor[0] = 1
|
||||
* scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
|
||||
* We apply a further scale factor of 8.
|
||||
* What's actually stored is 1/divisor so that the inner loop can
|
||||
* use a multiplication rather than a division.
|
||||
*/
|
||||
FAST_FLOAT *fdtbl;
|
||||
int row, col;
|
||||
static const double aanscalefactor[DCTSIZE] = {
|
||||
1.0, 1.387039845, 1.306562965, 1.175875602,
|
||||
1.0, 0.785694958, 0.541196100, 0.275899379
|
||||
};
|
||||
|
||||
if (fdct->float_divisors[qtblno] == NULL) {
|
||||
fdct->float_divisors[qtblno] = (FAST_FLOAT *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
DCTSIZE2 * sizeof(FAST_FLOAT));
|
||||
}
|
||||
fdtbl = fdct->float_divisors[qtblno];
|
||||
i = 0;
|
||||
for (row = 0; row < DCTSIZE; row++) {
|
||||
for (col = 0; col < DCTSIZE; col++) {
|
||||
fdtbl[i] = (FAST_FLOAT)
|
||||
(1.0 / (((double)qtbl->quantval[i] *
|
||||
aanscalefactor[row] * aanscalefactor[col] * 8.0)));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Load data into workspace, applying unsigned->signed conversion.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
convsamp(_JSAMPARRAY sample_data, JDIMENSION start_col, DCTELEM *workspace)
|
||||
{
|
||||
register DCTELEM *workspaceptr;
|
||||
register _JSAMPROW elemptr;
|
||||
register int elemr;
|
||||
|
||||
workspaceptr = workspace;
|
||||
for (elemr = 0; elemr < DCTSIZE; elemr++) {
|
||||
elemptr = sample_data[elemr] + start_col;
|
||||
|
||||
#if DCTSIZE == 8 /* unroll the inner loop */
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
#else
|
||||
{
|
||||
register int elemc;
|
||||
for (elemc = DCTSIZE; elemc > 0; elemc--)
|
||||
*workspaceptr++ = (*elemptr++) - _CENTERJSAMPLE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Quantize/descale the coefficients, and store into coef_blocks[].
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
quantize(JCOEFPTR coef_block, DCTELEM *divisors, DCTELEM *workspace)
|
||||
{
|
||||
int i;
|
||||
DCTELEM temp;
|
||||
JCOEFPTR output_ptr = coef_block;
|
||||
|
||||
#if BITS_IN_JSAMPLE == 8
|
||||
|
||||
UDCTELEM recip, corr;
|
||||
int shift;
|
||||
UDCTELEM2 product;
|
||||
|
||||
for (i = 0; i < DCTSIZE2; i++) {
|
||||
temp = workspace[i];
|
||||
recip = divisors[i + DCTSIZE2 * 0];
|
||||
corr = divisors[i + DCTSIZE2 * 1];
|
||||
shift = divisors[i + DCTSIZE2 * 3];
|
||||
|
||||
if (temp < 0) {
|
||||
temp = -temp;
|
||||
product = (UDCTELEM2)(temp + corr) * recip;
|
||||
product >>= shift + sizeof(DCTELEM) * 8;
|
||||
temp = (DCTELEM)product;
|
||||
temp = -temp;
|
||||
} else {
|
||||
product = (UDCTELEM2)(temp + corr) * recip;
|
||||
product >>= shift + sizeof(DCTELEM) * 8;
|
||||
temp = (DCTELEM)product;
|
||||
}
|
||||
output_ptr[i] = (JCOEF)temp;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
register DCTELEM qval;
|
||||
|
||||
for (i = 0; i < DCTSIZE2; i++) {
|
||||
qval = divisors[i];
|
||||
temp = workspace[i];
|
||||
/* Divide the coefficient value by qval, ensuring proper rounding.
|
||||
* Since C does not specify the direction of rounding for negative
|
||||
* quotients, we have to force the dividend positive for portability.
|
||||
*
|
||||
* In most files, at least half of the output values will be zero
|
||||
* (at default quantization settings, more like three-quarters...)
|
||||
* so we should ensure that this case is fast. On many machines,
|
||||
* a comparison is enough cheaper than a divide to make a special test
|
||||
* a win. Since both inputs will be nonnegative, we need only test
|
||||
* for a < b to discover whether a/b is 0.
|
||||
* If your machine's division is fast enough, define FAST_DIVIDE.
|
||||
*/
|
||||
#ifdef FAST_DIVIDE
|
||||
#define DIVIDE_BY(a, b) a /= b
|
||||
#else
|
||||
#define DIVIDE_BY(a, b) if (a >= b) a /= b; else a = 0
|
||||
#endif
|
||||
if (temp < 0) {
|
||||
temp = -temp;
|
||||
temp += qval >> 1; /* for rounding */
|
||||
DIVIDE_BY(temp, qval);
|
||||
temp = -temp;
|
||||
} else {
|
||||
temp += qval >> 1; /* for rounding */
|
||||
DIVIDE_BY(temp, qval);
|
||||
}
|
||||
output_ptr[i] = (JCOEF)temp;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Perform forward DCT on one or more blocks of a component.
|
||||
*
|
||||
* The input samples are taken from the sample_data[] array starting at
|
||||
* position start_row/start_col, and moving to the right for any additional
|
||||
* blocks. The quantized coefficients are returned in coef_blocks[].
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
forward_DCT(j_compress_ptr cinfo, jpeg_component_info *compptr,
|
||||
_JSAMPARRAY sample_data, JBLOCKROW coef_blocks,
|
||||
JDIMENSION start_row, JDIMENSION start_col, JDIMENSION num_blocks)
|
||||
/* This version is used for integer DCT implementations. */
|
||||
{
|
||||
/* This routine is heavily used, so it's worth coding it tightly. */
|
||||
my_fdct_ptr fdct = (my_fdct_ptr)cinfo->fdct;
|
||||
DCTELEM *divisors = fdct->divisors[compptr->quant_tbl_no];
|
||||
DCTELEM *workspace;
|
||||
JDIMENSION bi;
|
||||
|
||||
/* Make sure the compiler doesn't look up these every pass */
|
||||
forward_DCT_method_ptr do_dct = fdct->dct;
|
||||
convsamp_method_ptr do_convsamp = fdct->convsamp;
|
||||
quantize_method_ptr do_quantize = fdct->quantize;
|
||||
workspace = fdct->workspace;
|
||||
|
||||
sample_data += start_row; /* fold in the vertical offset once */
|
||||
|
||||
for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) {
|
||||
/* Load data into workspace, applying unsigned->signed conversion */
|
||||
(*do_convsamp) (sample_data, start_col, workspace);
|
||||
|
||||
/* Perform the DCT */
|
||||
(*do_dct) (workspace);
|
||||
|
||||
/* Quantize/descale the coefficients, and store into coef_blocks[] */
|
||||
(*do_quantize) (coef_blocks[bi], divisors, workspace);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef DCT_FLOAT_SUPPORTED
|
||||
|
||||
METHODDEF(void)
|
||||
convsamp_float(_JSAMPARRAY sample_data, JDIMENSION start_col,
|
||||
FAST_FLOAT *workspace)
|
||||
{
|
||||
register FAST_FLOAT *workspaceptr;
|
||||
register _JSAMPROW elemptr;
|
||||
register int elemr;
|
||||
|
||||
workspaceptr = workspace;
|
||||
for (elemr = 0; elemr < DCTSIZE; elemr++) {
|
||||
elemptr = sample_data[elemr] + start_col;
|
||||
#if DCTSIZE == 8 /* unroll the inner loop */
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
#else
|
||||
{
|
||||
register int elemc;
|
||||
for (elemc = DCTSIZE; elemc > 0; elemc--)
|
||||
*workspaceptr++ = (FAST_FLOAT)((*elemptr++) - _CENTERJSAMPLE);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
METHODDEF(void)
|
||||
quantize_float(JCOEFPTR coef_block, FAST_FLOAT *divisors,
|
||||
FAST_FLOAT *workspace)
|
||||
{
|
||||
register FAST_FLOAT temp;
|
||||
register int i;
|
||||
register JCOEFPTR output_ptr = coef_block;
|
||||
|
||||
for (i = 0; i < DCTSIZE2; i++) {
|
||||
/* Apply the quantization and scaling factor */
|
||||
temp = workspace[i] * divisors[i];
|
||||
|
||||
/* Round to nearest integer.
|
||||
* Since C does not specify the direction of rounding for negative
|
||||
* quotients, we have to force the dividend positive for portability.
|
||||
* The maximum coefficient size is +-16K (for 12-bit data), so this
|
||||
* code should work for either 16-bit or 32-bit ints.
|
||||
*/
|
||||
output_ptr[i] = (JCOEF)((int)(temp + (FAST_FLOAT)16384.5) - 16384);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
METHODDEF(void)
|
||||
forward_DCT_float(j_compress_ptr cinfo, jpeg_component_info *compptr,
|
||||
_JSAMPARRAY sample_data, JBLOCKROW coef_blocks,
|
||||
JDIMENSION start_row, JDIMENSION start_col,
|
||||
JDIMENSION num_blocks)
|
||||
/* This version is used for floating-point DCT implementations. */
|
||||
{
|
||||
/* This routine is heavily used, so it's worth coding it tightly. */
|
||||
my_fdct_ptr fdct = (my_fdct_ptr)cinfo->fdct;
|
||||
FAST_FLOAT *divisors = fdct->float_divisors[compptr->quant_tbl_no];
|
||||
FAST_FLOAT *workspace;
|
||||
JDIMENSION bi;
|
||||
|
||||
|
||||
/* Make sure the compiler doesn't look up these every pass */
|
||||
float_DCT_method_ptr do_dct = fdct->float_dct;
|
||||
float_convsamp_method_ptr do_convsamp = fdct->float_convsamp;
|
||||
float_quantize_method_ptr do_quantize = fdct->float_quantize;
|
||||
workspace = fdct->float_workspace;
|
||||
|
||||
sample_data += start_row; /* fold in the vertical offset once */
|
||||
|
||||
for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) {
|
||||
/* Load data into workspace, applying unsigned->signed conversion */
|
||||
(*do_convsamp) (sample_data, start_col, workspace);
|
||||
|
||||
/* Perform the DCT */
|
||||
(*do_dct) (workspace);
|
||||
|
||||
/* Quantize/descale the coefficients, and store into coef_blocks[] */
|
||||
(*do_quantize) (coef_blocks[bi], divisors, workspace);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DCT_FLOAT_SUPPORTED */
|
||||
|
||||
|
||||
/*
|
||||
* Initialize FDCT manager.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
_jinit_forward_dct(j_compress_ptr cinfo)
|
||||
{
|
||||
my_fdct_ptr fdct;
|
||||
int i;
|
||||
|
||||
if (cinfo->data_precision != BITS_IN_JSAMPLE)
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
|
||||
fdct = (my_fdct_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(my_fdct_controller));
|
||||
cinfo->fdct = (struct jpeg_forward_dct *)fdct;
|
||||
fdct->pub.start_pass = start_pass_fdctmgr;
|
||||
|
||||
/* First determine the DCT... */
|
||||
switch (cinfo->dct_method) {
|
||||
#ifdef DCT_ISLOW_SUPPORTED
|
||||
case JDCT_ISLOW:
|
||||
fdct->pub._forward_DCT = forward_DCT;
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_fdct_islow())
|
||||
fdct->dct = jsimd_fdct_islow;
|
||||
else
|
||||
#endif
|
||||
fdct->dct = _jpeg_fdct_islow;
|
||||
break;
|
||||
#endif
|
||||
#ifdef DCT_IFAST_SUPPORTED
|
||||
case JDCT_IFAST:
|
||||
fdct->pub._forward_DCT = forward_DCT;
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_fdct_ifast())
|
||||
fdct->dct = jsimd_fdct_ifast;
|
||||
else
|
||||
#endif
|
||||
fdct->dct = _jpeg_fdct_ifast;
|
||||
break;
|
||||
#endif
|
||||
#ifdef DCT_FLOAT_SUPPORTED
|
||||
case JDCT_FLOAT:
|
||||
fdct->pub._forward_DCT = forward_DCT_float;
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_fdct_float())
|
||||
fdct->float_dct = jsimd_fdct_float;
|
||||
else
|
||||
#endif
|
||||
fdct->float_dct = jpeg_fdct_float;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
break;
|
||||
}
|
||||
|
||||
/* ...then the supporting stages. */
|
||||
switch (cinfo->dct_method) {
|
||||
#ifdef DCT_ISLOW_SUPPORTED
|
||||
case JDCT_ISLOW:
|
||||
#endif
|
||||
#ifdef DCT_IFAST_SUPPORTED
|
||||
case JDCT_IFAST:
|
||||
#endif
|
||||
#if defined(DCT_ISLOW_SUPPORTED) || defined(DCT_IFAST_SUPPORTED)
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_convsamp())
|
||||
fdct->convsamp = jsimd_convsamp;
|
||||
else
|
||||
#endif
|
||||
fdct->convsamp = convsamp;
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_quantize())
|
||||
fdct->quantize = jsimd_quantize;
|
||||
else
|
||||
#endif
|
||||
fdct->quantize = quantize;
|
||||
break;
|
||||
#endif
|
||||
#ifdef DCT_FLOAT_SUPPORTED
|
||||
case JDCT_FLOAT:
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_convsamp_float())
|
||||
fdct->float_convsamp = jsimd_convsamp_float;
|
||||
else
|
||||
#endif
|
||||
fdct->float_convsamp = convsamp_float;
|
||||
#ifdef WITH_SIMD
|
||||
if (jsimd_can_quantize_float())
|
||||
fdct->float_quantize = jsimd_quantize_float;
|
||||
else
|
||||
#endif
|
||||
fdct->float_quantize = quantize_float;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Allocate workspace memory */
|
||||
#ifdef DCT_FLOAT_SUPPORTED
|
||||
if (cinfo->dct_method == JDCT_FLOAT)
|
||||
fdct->float_workspace = (FAST_FLOAT *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(FAST_FLOAT) * DCTSIZE2);
|
||||
else
|
||||
#endif
|
||||
fdct->workspace = (DCTELEM *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(DCTELEM) * DCTSIZE2);
|
||||
|
||||
/* Mark divisor tables unallocated */
|
||||
for (i = 0; i < NUM_QUANT_TBLS; i++) {
|
||||
fdct->divisors[i] = NULL;
|
||||
#ifdef DCT_FLOAT_SUPPORTED
|
||||
fdct->float_divisors[i] = NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* jcdiffct.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1994-1997, Thomas G. Lane.
|
||||
* Lossless JPEG Modifications:
|
||||
* Copyright (C) 1999, Ken Murchison.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains the difference buffer controller for compression.
|
||||
* This controller is the top level of the lossless JPEG compressor proper.
|
||||
* The difference buffer lies between the prediction/differencing and entropy
|
||||
* encoding steps.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jlossls.h" /* Private declarations for lossless codec */
|
||||
|
||||
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
|
||||
/* We use a full-image sample buffer when doing Huffman optimization,
|
||||
* and also for writing multiple-scan JPEG files. In all cases, the
|
||||
* full-image buffer is filled during the first pass, and the scaling,
|
||||
* prediction and differencing steps are run during subsequent passes.
|
||||
*/
|
||||
#ifdef ENTROPY_OPT_SUPPORTED
|
||||
#define FULL_SAMP_BUFFER_SUPPORTED
|
||||
#else
|
||||
#ifdef C_MULTISCAN_FILES_SUPPORTED
|
||||
#define FULL_SAMP_BUFFER_SUPPORTED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
/* Private buffer controller object */
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_c_coef_controller pub; /* public fields */
|
||||
|
||||
JDIMENSION iMCU_row_num; /* iMCU row # within image */
|
||||
JDIMENSION mcu_ctr; /* counts MCUs processed in current row */
|
||||
int MCU_vert_offset; /* counts MCU rows within iMCU row */
|
||||
int MCU_rows_per_iMCU_row; /* number of such rows needed */
|
||||
|
||||
_JSAMPROW cur_row[MAX_COMPONENTS]; /* row of point-transformed samples */
|
||||
_JSAMPROW prev_row[MAX_COMPONENTS]; /* previous row of Pt'd samples */
|
||||
JDIFFARRAY diff_buf[MAX_COMPONENTS]; /* iMCU row of differences */
|
||||
|
||||
/* In multi-pass modes, we need a virtual sample array for each component. */
|
||||
jvirt_sarray_ptr whole_image[MAX_COMPONENTS];
|
||||
} my_diff_controller;
|
||||
|
||||
typedef my_diff_controller *my_diff_ptr;
|
||||
|
||||
|
||||
/* Forward declarations */
|
||||
METHODDEF(boolean) compress_data(j_compress_ptr cinfo, _JSAMPIMAGE input_buf);
|
||||
#ifdef FULL_SAMP_BUFFER_SUPPORTED
|
||||
METHODDEF(boolean) compress_first_pass(j_compress_ptr cinfo,
|
||||
_JSAMPIMAGE input_buf);
|
||||
METHODDEF(boolean) compress_output(j_compress_ptr cinfo,
|
||||
_JSAMPIMAGE input_buf);
|
||||
#endif
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
start_iMCU_row(j_compress_ptr cinfo)
|
||||
/* Reset within-iMCU-row counters for a new row */
|
||||
{
|
||||
my_diff_ptr diff = (my_diff_ptr)cinfo->coef;
|
||||
|
||||
/* In an interleaved scan, an MCU row is the same as an iMCU row.
|
||||
* In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows.
|
||||
* But at the bottom of the image, process only what's left.
|
||||
*/
|
||||
if (cinfo->comps_in_scan > 1) {
|
||||
diff->MCU_rows_per_iMCU_row = 1;
|
||||
} else {
|
||||
if (diff->iMCU_row_num < (cinfo->total_iMCU_rows-1))
|
||||
diff->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor;
|
||||
else
|
||||
diff->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height;
|
||||
}
|
||||
|
||||
diff->mcu_ctr = 0;
|
||||
diff->MCU_vert_offset = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize for a processing pass.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
start_pass_diff(j_compress_ptr cinfo, J_BUF_MODE pass_mode)
|
||||
{
|
||||
my_diff_ptr diff = (my_diff_ptr)cinfo->coef;
|
||||
|
||||
/* Because it is hitching a ride on the jpeg_forward_dct struct,
|
||||
* start_pass_lossless() will be called at the start of the initial pass.
|
||||
* This ensures that it will be called at the start of the Huffman
|
||||
* optimization and output passes as well.
|
||||
*/
|
||||
if (pass_mode == JBUF_CRANK_DEST)
|
||||
(*cinfo->fdct->start_pass) (cinfo);
|
||||
|
||||
diff->iMCU_row_num = 0;
|
||||
start_iMCU_row(cinfo);
|
||||
|
||||
switch (pass_mode) {
|
||||
case JBUF_PASS_THRU:
|
||||
if (diff->whole_image[0] != NULL)
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
diff->pub._compress_data = compress_data;
|
||||
break;
|
||||
#ifdef FULL_SAMP_BUFFER_SUPPORTED
|
||||
case JBUF_SAVE_AND_PASS:
|
||||
if (diff->whole_image[0] == NULL)
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
diff->pub._compress_data = compress_first_pass;
|
||||
break;
|
||||
case JBUF_CRANK_DEST:
|
||||
if (diff->whole_image[0] == NULL)
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
diff->pub._compress_data = compress_output;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define SWAP_ROWS(rowa, rowb) { \
|
||||
_JSAMPROW temp = rowa; \
|
||||
rowa = rowb; rowb = temp; \
|
||||
}
|
||||
|
||||
/*
|
||||
* Process some data in the single-pass case.
|
||||
* We process the equivalent of one fully interleaved MCU row ("iMCU" row)
|
||||
* per call, ie, v_samp_factor rows for each component in the image.
|
||||
* Returns TRUE if the iMCU row is completed, FALSE if suspended.
|
||||
*
|
||||
* NB: input_buf contains a plane for each component in image,
|
||||
* which we index according to the component's SOF position.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
compress_data(j_compress_ptr cinfo, _JSAMPIMAGE input_buf)
|
||||
{
|
||||
my_diff_ptr diff = (my_diff_ptr)cinfo->coef;
|
||||
lossless_comp_ptr losslessc = (lossless_comp_ptr)cinfo->fdct;
|
||||
JDIMENSION MCU_col_num; /* index of current MCU within row */
|
||||
JDIMENSION MCU_count; /* number of MCUs encoded */
|
||||
JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
|
||||
int ci, compi, yoffset, samp_row, samp_rows, samps_across;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
/* Loop to write as much as one whole iMCU row */
|
||||
for (yoffset = diff->MCU_vert_offset; yoffset < diff->MCU_rows_per_iMCU_row;
|
||||
yoffset++) {
|
||||
|
||||
MCU_col_num = diff->mcu_ctr;
|
||||
|
||||
/* Scale and predict each scanline of the MCU row separately.
|
||||
*
|
||||
* Note: We only do this if we are at the start of an MCU row, ie,
|
||||
* we don't want to reprocess a row suspended by the output.
|
||||
*/
|
||||
if (MCU_col_num == 0) {
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
compi = compptr->component_index;
|
||||
if (diff->iMCU_row_num < last_iMCU_row)
|
||||
samp_rows = compptr->v_samp_factor;
|
||||
else {
|
||||
/* NB: can't use last_row_height here, since may not be set! */
|
||||
samp_rows =
|
||||
(int)(compptr->height_in_blocks % compptr->v_samp_factor);
|
||||
if (samp_rows == 0) samp_rows = compptr->v_samp_factor;
|
||||
else {
|
||||
/* Fill dummy difference rows at the bottom edge with zeros, which
|
||||
* will encode to the smallest amount of data.
|
||||
*/
|
||||
for (samp_row = samp_rows; samp_row < compptr->v_samp_factor;
|
||||
samp_row++)
|
||||
memset(diff->diff_buf[compi][samp_row], 0,
|
||||
jround_up((long)compptr->width_in_blocks,
|
||||
(long)compptr->h_samp_factor) * sizeof(JDIFF));
|
||||
}
|
||||
}
|
||||
samps_across = compptr->width_in_blocks;
|
||||
|
||||
for (samp_row = 0; samp_row < samp_rows; samp_row++) {
|
||||
(*losslessc->scaler_scale) (cinfo,
|
||||
input_buf[compi][samp_row],
|
||||
diff->cur_row[compi],
|
||||
samps_across);
|
||||
(*losslessc->predict_difference[compi])
|
||||
(cinfo, compi, diff->cur_row[compi], diff->prev_row[compi],
|
||||
diff->diff_buf[compi][samp_row], samps_across);
|
||||
SWAP_ROWS(diff->cur_row[compi], diff->prev_row[compi]);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Try to write the MCU row (or remaining portion of suspended MCU row). */
|
||||
MCU_count =
|
||||
(*cinfo->entropy->encode_mcus) (cinfo,
|
||||
diff->diff_buf, yoffset, MCU_col_num,
|
||||
cinfo->MCUs_per_row - MCU_col_num);
|
||||
if (MCU_count != cinfo->MCUs_per_row - MCU_col_num) {
|
||||
/* Suspension forced; update state counters and exit */
|
||||
diff->MCU_vert_offset = yoffset;
|
||||
diff->mcu_ctr += MCU_col_num;
|
||||
return FALSE;
|
||||
}
|
||||
/* Completed an MCU row, but perhaps not an iMCU row */
|
||||
diff->mcu_ctr = 0;
|
||||
}
|
||||
/* Completed the iMCU row, advance counters for next one */
|
||||
diff->iMCU_row_num++;
|
||||
start_iMCU_row(cinfo);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
#ifdef FULL_SAMP_BUFFER_SUPPORTED
|
||||
|
||||
/*
|
||||
* Process some data in the first pass of a multi-pass case.
|
||||
* We process the equivalent of one fully interleaved MCU row ("iMCU" row)
|
||||
* per call, ie, v_samp_factor rows for each component in the image.
|
||||
* This amount of data is read from the source buffer and saved into the
|
||||
* virtual arrays.
|
||||
*
|
||||
* We must also emit the data to the compressor. This is conveniently
|
||||
* done by calling compress_output() after we've loaded the current strip
|
||||
* of the virtual arrays.
|
||||
*
|
||||
* NB: input_buf contains a plane for each component in image. All components
|
||||
* are loaded into the virtual arrays in this pass. However, it may be that
|
||||
* only a subset of the components are emitted to the compressor during
|
||||
* this first pass; be careful about looking at the scan-dependent variables
|
||||
* (MCU dimensions, etc).
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
compress_first_pass(j_compress_ptr cinfo, _JSAMPIMAGE input_buf)
|
||||
{
|
||||
my_diff_ptr diff = (my_diff_ptr)cinfo->coef;
|
||||
JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1;
|
||||
JDIMENSION samps_across;
|
||||
int ci, samp_row, samp_rows;
|
||||
_JSAMPARRAY buffer;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
/* Align the virtual buffer for this component. */
|
||||
buffer = (_JSAMPARRAY)(*cinfo->mem->access_virt_sarray)
|
||||
((j_common_ptr)cinfo, diff->whole_image[ci],
|
||||
diff->iMCU_row_num * compptr->v_samp_factor,
|
||||
(JDIMENSION)compptr->v_samp_factor, TRUE);
|
||||
|
||||
/* Count non-dummy sample rows in this iMCU row. */
|
||||
if (diff->iMCU_row_num < last_iMCU_row)
|
||||
samp_rows = compptr->v_samp_factor;
|
||||
else {
|
||||
/* NB: can't use last_row_height here, since may not be set! */
|
||||
samp_rows = (int)(compptr->height_in_blocks % compptr->v_samp_factor);
|
||||
if (samp_rows == 0) samp_rows = compptr->v_samp_factor;
|
||||
}
|
||||
samps_across = compptr->width_in_blocks;
|
||||
|
||||
/* Perform point transform scaling and prediction/differencing for all
|
||||
* non-dummy rows in this iMCU row. Each call on these functions
|
||||
* processes a complete row of samples.
|
||||
*/
|
||||
for (samp_row = 0; samp_row < samp_rows; samp_row++) {
|
||||
memcpy(buffer[samp_row], input_buf[ci][samp_row],
|
||||
samps_across * sizeof(_JSAMPLE));
|
||||
}
|
||||
}
|
||||
/* NB: compress_output will increment iMCU_row_num if successful.
|
||||
* A suspension return will result in redoing all the work above next time.
|
||||
*/
|
||||
|
||||
/* Emit data to the compressor, sharing code with subsequent passes */
|
||||
return compress_output(cinfo, input_buf);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Process some data in subsequent passes of a multi-pass case.
|
||||
* We process the equivalent of one fully interleaved MCU row ("iMCU" row)
|
||||
* per call, ie, v_samp_factor rows for each component in the scan.
|
||||
* The data is obtained from the virtual arrays and fed to the compressor.
|
||||
* Returns TRUE if the iMCU row is completed, FALSE if suspended.
|
||||
*
|
||||
* NB: input_buf is ignored; it is likely to be a NULL pointer.
|
||||
*/
|
||||
|
||||
METHODDEF(boolean)
|
||||
compress_output(j_compress_ptr cinfo, _JSAMPIMAGE input_buf)
|
||||
{
|
||||
my_diff_ptr diff = (my_diff_ptr)cinfo->coef;
|
||||
int ci, compi;
|
||||
_JSAMPARRAY buffer[MAX_COMPS_IN_SCAN];
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
/* Align the virtual buffers for the components used in this scan.
|
||||
* NB: during first pass, this is safe only because the buffers will
|
||||
* already be aligned properly, so jmemmgr.c won't need to do any I/O.
|
||||
*/
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
compi = compptr->component_index;
|
||||
buffer[compi] = (_JSAMPARRAY)(*cinfo->mem->access_virt_sarray)
|
||||
((j_common_ptr)cinfo, diff->whole_image[compi],
|
||||
diff->iMCU_row_num * compptr->v_samp_factor,
|
||||
(JDIMENSION)compptr->v_samp_factor, FALSE);
|
||||
}
|
||||
|
||||
return compress_data(cinfo, buffer);
|
||||
}
|
||||
|
||||
#endif /* FULL_SAMP_BUFFER_SUPPORTED */
|
||||
|
||||
|
||||
/*
|
||||
* Initialize difference buffer controller.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
_jinit_c_diff_controller(j_compress_ptr cinfo, boolean need_full_buffer)
|
||||
{
|
||||
my_diff_ptr diff;
|
||||
int ci, row;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
diff = (my_diff_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(my_diff_controller));
|
||||
cinfo->coef = (struct jpeg_c_coef_controller *)diff;
|
||||
diff->pub.start_pass = start_pass_diff;
|
||||
|
||||
/* Create the prediction row buffers. */
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
diff->cur_row[ci] = *(_JSAMPARRAY)(*cinfo->mem->alloc_sarray)
|
||||
((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
(JDIMENSION)jround_up((long)compptr->width_in_blocks,
|
||||
(long)compptr->h_samp_factor),
|
||||
(JDIMENSION)1);
|
||||
diff->prev_row[ci] = *(_JSAMPARRAY)(*cinfo->mem->alloc_sarray)
|
||||
((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
(JDIMENSION)jround_up((long)compptr->width_in_blocks,
|
||||
(long)compptr->h_samp_factor),
|
||||
(JDIMENSION)1);
|
||||
}
|
||||
|
||||
/* Create the difference buffer. */
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
diff->diff_buf[ci] =
|
||||
ALLOC_DARRAY(JPOOL_IMAGE,
|
||||
(JDIMENSION)jround_up((long)compptr->width_in_blocks,
|
||||
(long)compptr->h_samp_factor),
|
||||
(JDIMENSION)compptr->v_samp_factor);
|
||||
/* Prefill difference rows with zeros. We do this because only actual
|
||||
* data is placed in the buffers during prediction/differencing, leaving
|
||||
* any dummy differences at the right edge as zeros, which will encode
|
||||
* to the smallest amount of data.
|
||||
*/
|
||||
for (row = 0; row < compptr->v_samp_factor; row++)
|
||||
memset(diff->diff_buf[ci][row], 0,
|
||||
jround_up((long)compptr->width_in_blocks,
|
||||
(long)compptr->h_samp_factor) * sizeof(JDIFF));
|
||||
}
|
||||
|
||||
/* Create the sample buffer. */
|
||||
if (need_full_buffer) {
|
||||
#ifdef FULL_SAMP_BUFFER_SUPPORTED
|
||||
/* Allocate a full-image virtual array for each component, */
|
||||
/* padded to a multiple of samp_factor differences in each direction. */
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
diff->whole_image[ci] = (*cinfo->mem->request_virt_sarray)
|
||||
((j_common_ptr)cinfo, JPOOL_IMAGE, FALSE,
|
||||
(JDIMENSION)jround_up((long)compptr->width_in_blocks,
|
||||
(long)compptr->h_samp_factor),
|
||||
(JDIMENSION)jround_up((long)compptr->height_in_blocks,
|
||||
(long)compptr->v_samp_factor),
|
||||
(JDIMENSION)compptr->v_samp_factor);
|
||||
}
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
#endif
|
||||
} else
|
||||
diff->whole_image[0] = NULL; /* flag for no virtual arrays */
|
||||
}
|
||||
|
||||
#endif /* C_LOSSLESS_SUPPORTED */
|
||||
+1175
File diff suppressed because it is too large
Load Diff
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* jchuff.h
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1991-1997, Thomas G. Lane.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains declarations for Huffman entropy encoding routines
|
||||
* that are shared between the sequential encoder (jchuff.c) and the
|
||||
* progressive encoder (jcphuff.c). No other modules need to see these.
|
||||
*/
|
||||
|
||||
/* The legal range of a DCT coefficient is
|
||||
* -1024 .. +1023 for 8-bit data;
|
||||
* -16384 .. +16383 for 12-bit data.
|
||||
* Hence the magnitude should always fit in 10 or 14 bits respectively.
|
||||
*/
|
||||
|
||||
/* The progressive Huffman encoder uses an unsigned 16-bit data type to store
|
||||
* absolute values of coefficients, because it is possible to inject a
|
||||
* coefficient value of -32768 into the encoder by attempting to transform a
|
||||
* malformed 12-bit JPEG image, and the absolute value of -32768 would overflow
|
||||
* a signed 16-bit integer.
|
||||
*/
|
||||
typedef unsigned short UJCOEF;
|
||||
|
||||
/* Derived data constructed for each Huffman table */
|
||||
|
||||
typedef struct {
|
||||
unsigned int ehufco[256]; /* code for each symbol */
|
||||
char ehufsi[256]; /* length of code for each symbol */
|
||||
/* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */
|
||||
} c_derived_tbl;
|
||||
|
||||
/* Expand a Huffman table definition into the derived format */
|
||||
EXTERN(void) jpeg_make_c_derived_tbl(j_compress_ptr cinfo, boolean isDC,
|
||||
int tblno, c_derived_tbl **pdtbl);
|
||||
|
||||
/* Generate an optimal table definition given the specified counts */
|
||||
EXTERN(void) jpeg_gen_optimal_table(j_compress_ptr cinfo, JHUFF_TBL *htbl,
|
||||
long freq[]);
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* jcicc.c
|
||||
*
|
||||
* Copyright (C) 1997-1998, Thomas G. Lane, Todd Newman.
|
||||
* Copyright (C) 2017, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file provides code to write International Color Consortium (ICC) device
|
||||
* profiles embedded in JFIF JPEG image files. The ICC has defined a standard
|
||||
* for including such data in JPEG "APP2" markers. The code given here does
|
||||
* not know anything about the internal structure of the ICC profile data; it
|
||||
* just knows how to embed the profile data in a JPEG file while writing it.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jerror.h"
|
||||
|
||||
|
||||
/*
|
||||
* Since an ICC profile can be larger than the maximum size of a JPEG marker
|
||||
* (64K), we need provisions to split it into multiple markers. The format
|
||||
* defined by the ICC specifies one or more APP2 markers containing the
|
||||
* following data:
|
||||
* Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
|
||||
* Marker sequence number 1 for first APP2, 2 for next, etc (1 byte)
|
||||
* Number of markers Total number of APP2's used (1 byte)
|
||||
* Profile data (remainder of APP2 data)
|
||||
* Decoders should use the marker sequence numbers to reassemble the profile,
|
||||
* rather than assuming that the APP2 markers appear in the correct sequence.
|
||||
*/
|
||||
|
||||
#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
|
||||
#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
|
||||
#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
|
||||
#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
|
||||
|
||||
|
||||
/*
|
||||
* This routine writes the given ICC profile data into a JPEG file. It *must*
|
||||
* be called AFTER calling jpeg_start_compress() and BEFORE the first call to
|
||||
* jpeg_write_scanlines(). (This ordering ensures that the APP2 marker(s) will
|
||||
* appear after the SOI and JFIF or Adobe markers, but before all else.)
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_write_icc_profile(j_compress_ptr cinfo, const JOCTET *icc_data_ptr,
|
||||
unsigned int icc_data_len)
|
||||
{
|
||||
unsigned int num_markers; /* total number of markers we'll write */
|
||||
int cur_marker = 1; /* per spec, counting starts at 1 */
|
||||
unsigned int length; /* number of bytes to write in this marker */
|
||||
|
||||
if (icc_data_ptr == NULL || icc_data_len == 0)
|
||||
ERREXIT(cinfo, JERR_BUFFER_SIZE);
|
||||
if (cinfo->global_state < CSTATE_SCANNING)
|
||||
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
||||
|
||||
/* Calculate the number of markers we'll need, rounding up of course */
|
||||
num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER;
|
||||
if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len)
|
||||
num_markers++;
|
||||
|
||||
while (icc_data_len > 0) {
|
||||
/* length of profile to put in this marker */
|
||||
length = icc_data_len;
|
||||
if (length > MAX_DATA_BYTES_IN_MARKER)
|
||||
length = MAX_DATA_BYTES_IN_MARKER;
|
||||
icc_data_len -= length;
|
||||
|
||||
/* Write the JPEG marker header (APP2 code and marker length) */
|
||||
jpeg_write_m_header(cinfo, ICC_MARKER,
|
||||
(unsigned int)(length + ICC_OVERHEAD_LEN));
|
||||
|
||||
/* Write the marker identifying string "ICC_PROFILE" (null-terminated). We
|
||||
* code it in this less-than-transparent way so that the code works even if
|
||||
* the local character set is not ASCII.
|
||||
*/
|
||||
jpeg_write_m_byte(cinfo, 0x49);
|
||||
jpeg_write_m_byte(cinfo, 0x43);
|
||||
jpeg_write_m_byte(cinfo, 0x43);
|
||||
jpeg_write_m_byte(cinfo, 0x5F);
|
||||
jpeg_write_m_byte(cinfo, 0x50);
|
||||
jpeg_write_m_byte(cinfo, 0x52);
|
||||
jpeg_write_m_byte(cinfo, 0x4F);
|
||||
jpeg_write_m_byte(cinfo, 0x46);
|
||||
jpeg_write_m_byte(cinfo, 0x49);
|
||||
jpeg_write_m_byte(cinfo, 0x4C);
|
||||
jpeg_write_m_byte(cinfo, 0x45);
|
||||
jpeg_write_m_byte(cinfo, 0x0);
|
||||
|
||||
/* Add the sequencing info */
|
||||
jpeg_write_m_byte(cinfo, cur_marker);
|
||||
jpeg_write_m_byte(cinfo, (int)num_markers);
|
||||
|
||||
/* Add the profile data */
|
||||
while (length--) {
|
||||
jpeg_write_m_byte(cinfo, *icc_data_ptr);
|
||||
icc_data_ptr++;
|
||||
}
|
||||
cur_marker++;
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* jcinit.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1991-1997, Thomas G. Lane.
|
||||
* Lossless JPEG Modifications:
|
||||
* Copyright (C) 1999, Ken Murchison.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2020, 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains initialization logic for the JPEG compressor.
|
||||
* This routine is in charge of selecting the modules to be executed and
|
||||
* making an initialization call to each one.
|
||||
*
|
||||
* Logically, this code belongs in jcmaster.c. It's split out because
|
||||
* linking this routine implies linking the entire compression library.
|
||||
* For a transcoding-only application, we want to be able to use jcmaster.c
|
||||
* without linking in the whole library.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jpegapicomp.h"
|
||||
|
||||
|
||||
/*
|
||||
* Master selection of compression modules.
|
||||
* This is done once at the start of processing an image. We determine
|
||||
* which modules will be used and give them appropriate initialization calls.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jinit_compress_master(j_compress_ptr cinfo)
|
||||
{
|
||||
/* Initialize master control (includes parameter checking/processing) */
|
||||
jinit_c_master_control(cinfo, FALSE /* full compression */);
|
||||
|
||||
/* Preprocessing */
|
||||
if (!cinfo->raw_data_in) {
|
||||
if (cinfo->data_precision == 16) {
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
j16init_color_converter(cinfo);
|
||||
j16init_downsampler(cinfo);
|
||||
j16init_c_prep_controller(cinfo,
|
||||
FALSE /* never need full buffer here */);
|
||||
#else
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
#endif
|
||||
} else if (cinfo->data_precision == 12) {
|
||||
j12init_color_converter(cinfo);
|
||||
j12init_downsampler(cinfo);
|
||||
j12init_c_prep_controller(cinfo,
|
||||
FALSE /* never need full buffer here */);
|
||||
} else {
|
||||
jinit_color_converter(cinfo);
|
||||
jinit_downsampler(cinfo);
|
||||
jinit_c_prep_controller(cinfo, FALSE /* never need full buffer here */);
|
||||
}
|
||||
}
|
||||
|
||||
if (cinfo->master->lossless) {
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
/* Prediction, sample differencing, and point transform */
|
||||
if (cinfo->data_precision == 16)
|
||||
j16init_lossless_compressor(cinfo);
|
||||
else if (cinfo->data_precision == 12)
|
||||
j12init_lossless_compressor(cinfo);
|
||||
else
|
||||
jinit_lossless_compressor(cinfo);
|
||||
/* Entropy encoding: either Huffman or arithmetic coding. */
|
||||
if (cinfo->arith_code) {
|
||||
ERREXIT(cinfo, JERR_ARITH_NOTIMPL);
|
||||
} else {
|
||||
jinit_lhuff_encoder(cinfo);
|
||||
}
|
||||
|
||||
/* Need a full-image difference buffer in any multi-pass mode. */
|
||||
if (cinfo->data_precision == 16)
|
||||
j16init_c_diff_controller(cinfo, (boolean)(cinfo->num_scans > 1 ||
|
||||
cinfo->optimize_coding));
|
||||
else if (cinfo->data_precision == 12)
|
||||
j12init_c_diff_controller(cinfo, (boolean)(cinfo->num_scans > 1 ||
|
||||
cinfo->optimize_coding));
|
||||
else
|
||||
jinit_c_diff_controller(cinfo, (boolean)(cinfo->num_scans > 1 ||
|
||||
cinfo->optimize_coding));
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
#endif
|
||||
} else {
|
||||
if (cinfo->data_precision == 16)
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
/* Forward DCT */
|
||||
if (cinfo->data_precision == 12)
|
||||
j12init_forward_dct(cinfo);
|
||||
else
|
||||
jinit_forward_dct(cinfo);
|
||||
/* Entropy encoding: either Huffman or arithmetic coding. */
|
||||
if (cinfo->arith_code) {
|
||||
#ifdef C_ARITH_CODING_SUPPORTED
|
||||
jinit_arith_encoder(cinfo);
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_ARITH_NOTIMPL);
|
||||
#endif
|
||||
} else {
|
||||
if (cinfo->progressive_mode) {
|
||||
#ifdef C_PROGRESSIVE_SUPPORTED
|
||||
jinit_phuff_encoder(cinfo);
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
#endif
|
||||
} else
|
||||
jinit_huff_encoder(cinfo);
|
||||
}
|
||||
|
||||
/* Need a full-image coefficient buffer in any multi-pass mode. */
|
||||
if (cinfo->data_precision == 12)
|
||||
j12init_c_coef_controller(cinfo, (boolean)(cinfo->num_scans > 1 ||
|
||||
cinfo->optimize_coding));
|
||||
else
|
||||
jinit_c_coef_controller(cinfo, (boolean)(cinfo->num_scans > 1 ||
|
||||
cinfo->optimize_coding));
|
||||
}
|
||||
|
||||
if (cinfo->data_precision == 16)
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
j16init_c_main_controller(cinfo, FALSE /* never need full buffer here */);
|
||||
#else
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
#endif
|
||||
else if (cinfo->data_precision == 12)
|
||||
j12init_c_main_controller(cinfo, FALSE /* never need full buffer here */);
|
||||
else
|
||||
jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */);
|
||||
|
||||
jinit_marker_writer(cinfo);
|
||||
|
||||
/* We can now tell the memory manager to allocate virtual arrays. */
|
||||
(*cinfo->mem->realize_virt_arrays) ((j_common_ptr)cinfo);
|
||||
|
||||
/* Write the datastream header (SOI) immediately.
|
||||
* Frame and scan headers are postponed till later.
|
||||
* This lets application insert special markers after the SOI.
|
||||
*/
|
||||
(*cinfo->marker->write_file_header) (cinfo);
|
||||
}
|
||||
+587
@@ -0,0 +1,587 @@
|
||||
/*
|
||||
* jclhuff.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1991-1997, Thomas G. Lane.
|
||||
* Lossless JPEG Modifications:
|
||||
* Copyright (C) 1999, Ken Murchison.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains Huffman entropy encoding routines for lossless JPEG.
|
||||
*
|
||||
* Much of the complexity here has to do with supporting output suspension.
|
||||
* If the data destination module demands suspension, we want to be able to
|
||||
* back up to the start of the current MCU. To do this, we copy state
|
||||
* variables into local working storage, and update them back to the
|
||||
* permanent JPEG objects only upon successful completion of an MCU.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jlossls.h" /* Private declarations for lossless codec */
|
||||
#include "jchuff.h" /* Declarations shared with jc*huff.c */
|
||||
|
||||
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
|
||||
/* The legal range of a spatial difference is
|
||||
* -32767 .. +32768.
|
||||
* Hence the magnitude should always fit in 16 bits.
|
||||
*/
|
||||
|
||||
#define MAX_DIFF_BITS 16
|
||||
|
||||
|
||||
/* Expanded entropy encoder object for Huffman encoding in lossless mode.
|
||||
*
|
||||
* The savable_state subrecord contains fields that change within an MCU,
|
||||
* but must not be updated permanently until we complete the MCU.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
size_t put_buffer; /* current bit-accumulation buffer */
|
||||
int put_bits; /* # of bits now in it */
|
||||
} savable_state;
|
||||
|
||||
|
||||
typedef struct {
|
||||
int ci, yoffset, MCU_width;
|
||||
} lhe_input_ptr_info;
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_entropy_encoder pub; /* public fields */
|
||||
|
||||
savable_state saved; /* Bit buffer at start of MCU */
|
||||
|
||||
/* These fields are NOT loaded into local working state. */
|
||||
unsigned int restarts_to_go; /* MCUs left in this restart interval */
|
||||
int next_restart_num; /* next restart number to write (0-7) */
|
||||
|
||||
/* Pointers to derived tables (these workspaces have image lifespan) */
|
||||
c_derived_tbl *derived_tbls[NUM_HUFF_TBLS];
|
||||
|
||||
/* Pointers to derived tables to be used for each data unit within an MCU */
|
||||
c_derived_tbl *cur_tbls[C_MAX_BLOCKS_IN_MCU];
|
||||
|
||||
#ifdef ENTROPY_OPT_SUPPORTED /* Statistics tables for optimization */
|
||||
long *count_ptrs[NUM_HUFF_TBLS];
|
||||
|
||||
/* Pointers to stats tables to be used for each data unit within an MCU */
|
||||
long *cur_counts[C_MAX_BLOCKS_IN_MCU];
|
||||
#endif
|
||||
|
||||
/* Pointers to the proper input difference row for each group of data units
|
||||
* within an MCU. For each component, there are Vi groups of Hi data units.
|
||||
*/
|
||||
JDIFFROW input_ptr[C_MAX_BLOCKS_IN_MCU];
|
||||
|
||||
/* Number of input pointers in use for the current MCU. This is the sum
|
||||
* of all Vi in the MCU.
|
||||
*/
|
||||
int num_input_ptrs;
|
||||
|
||||
/* Information used for positioning the input pointers within the input
|
||||
* difference rows.
|
||||
*/
|
||||
lhe_input_ptr_info input_ptr_info[C_MAX_BLOCKS_IN_MCU];
|
||||
|
||||
/* Index of the proper input pointer for each data unit within an MCU */
|
||||
int input_ptr_index[C_MAX_BLOCKS_IN_MCU];
|
||||
|
||||
} lhuff_entropy_encoder;
|
||||
|
||||
typedef lhuff_entropy_encoder *lhuff_entropy_ptr;
|
||||
|
||||
/* Working state while writing an MCU.
|
||||
* This struct contains all the fields that are needed by subroutines.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
JOCTET *next_output_byte; /* => next byte to write in buffer */
|
||||
size_t free_in_buffer; /* # of byte spaces remaining in buffer */
|
||||
savable_state cur; /* Current bit buffer & DC state */
|
||||
j_compress_ptr cinfo; /* dump_buffer needs access to this */
|
||||
} working_state;
|
||||
|
||||
|
||||
/* Forward declarations */
|
||||
METHODDEF(JDIMENSION) encode_mcus_huff(j_compress_ptr cinfo,
|
||||
JDIFFIMAGE diff_buf,
|
||||
JDIMENSION MCU_row_num,
|
||||
JDIMENSION MCU_col_num,
|
||||
JDIMENSION nMCU);
|
||||
METHODDEF(void) finish_pass_huff(j_compress_ptr cinfo);
|
||||
#ifdef ENTROPY_OPT_SUPPORTED
|
||||
METHODDEF(JDIMENSION) encode_mcus_gather(j_compress_ptr cinfo,
|
||||
JDIFFIMAGE diff_buf,
|
||||
JDIMENSION MCU_row_num,
|
||||
JDIMENSION MCU_col_num,
|
||||
JDIMENSION nMCU);
|
||||
METHODDEF(void) finish_pass_gather(j_compress_ptr cinfo);
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Initialize for a Huffman-compressed scan.
|
||||
* If gather_statistics is TRUE, we do not output anything during the scan,
|
||||
* just count the Huffman symbols used and generate Huffman code tables.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
start_pass_lhuff(j_compress_ptr cinfo, boolean gather_statistics)
|
||||
{
|
||||
lhuff_entropy_ptr entropy = (lhuff_entropy_ptr)cinfo->entropy;
|
||||
int ci, dctbl, sampn, ptrn, yoffset, xoffset;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
if (gather_statistics) {
|
||||
#ifdef ENTROPY_OPT_SUPPORTED
|
||||
entropy->pub.encode_mcus = encode_mcus_gather;
|
||||
entropy->pub.finish_pass = finish_pass_gather;
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
#endif
|
||||
} else {
|
||||
entropy->pub.encode_mcus = encode_mcus_huff;
|
||||
entropy->pub.finish_pass = finish_pass_huff;
|
||||
}
|
||||
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
dctbl = compptr->dc_tbl_no;
|
||||
if (gather_statistics) {
|
||||
#ifdef ENTROPY_OPT_SUPPORTED
|
||||
/* Check for invalid table indexes */
|
||||
/* (make_c_derived_tbl does this in the other path) */
|
||||
if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS)
|
||||
ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl);
|
||||
/* Allocate and zero the statistics tables */
|
||||
/* Note that jpeg_gen_optimal_table expects 257 entries in each table! */
|
||||
if (entropy->count_ptrs[dctbl] == NULL)
|
||||
entropy->count_ptrs[dctbl] = (long *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
257 * sizeof(long));
|
||||
memset(entropy->count_ptrs[dctbl], 0, 257 * sizeof(long));
|
||||
#endif
|
||||
} else {
|
||||
/* Compute derived values for Huffman tables */
|
||||
/* We may do this more than once for a table, but it's not expensive */
|
||||
jpeg_make_c_derived_tbl(cinfo, TRUE, dctbl,
|
||||
&entropy->derived_tbls[dctbl]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Precalculate encoding info for each sample in an MCU of this scan */
|
||||
for (sampn = 0, ptrn = 0; sampn < cinfo->blocks_in_MCU;) {
|
||||
compptr = cinfo->cur_comp_info[cinfo->MCU_membership[sampn]];
|
||||
ci = compptr->component_index;
|
||||
for (yoffset = 0; yoffset < compptr->MCU_height; yoffset++, ptrn++) {
|
||||
/* Precalculate the setup info for each input pointer */
|
||||
entropy->input_ptr_info[ptrn].ci = ci;
|
||||
entropy->input_ptr_info[ptrn].yoffset = yoffset;
|
||||
entropy->input_ptr_info[ptrn].MCU_width = compptr->MCU_width;
|
||||
for (xoffset = 0; xoffset < compptr->MCU_width; xoffset++, sampn++) {
|
||||
/* Precalculate the input pointer index for each sample */
|
||||
entropy->input_ptr_index[sampn] = ptrn;
|
||||
/* Precalculate which tables to use for each sample */
|
||||
entropy->cur_tbls[sampn] = entropy->derived_tbls[compptr->dc_tbl_no];
|
||||
entropy->cur_counts[sampn] = entropy->count_ptrs[compptr->dc_tbl_no];
|
||||
}
|
||||
}
|
||||
}
|
||||
entropy->num_input_ptrs = ptrn;
|
||||
|
||||
/* Initialize bit buffer to empty */
|
||||
entropy->saved.put_buffer = 0;
|
||||
entropy->saved.put_bits = 0;
|
||||
|
||||
/* Initialize restart stuff */
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
entropy->next_restart_num = 0;
|
||||
}
|
||||
|
||||
|
||||
/* Outputting bytes to the file */
|
||||
|
||||
/* Emit a byte, taking 'action' if must suspend. */
|
||||
#define emit_byte(state, val, action) { \
|
||||
*(state)->next_output_byte++ = (JOCTET)(val); \
|
||||
if (--(state)->free_in_buffer == 0) \
|
||||
if (!dump_buffer(state)) \
|
||||
{ action; } \
|
||||
}
|
||||
|
||||
|
||||
LOCAL(boolean)
|
||||
dump_buffer(working_state *state)
|
||||
/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */
|
||||
{
|
||||
struct jpeg_destination_mgr *dest = state->cinfo->dest;
|
||||
|
||||
if (!(*dest->empty_output_buffer) (state->cinfo))
|
||||
return FALSE;
|
||||
/* After a successful buffer dump, must reset buffer pointers */
|
||||
state->next_output_byte = dest->next_output_byte;
|
||||
state->free_in_buffer = dest->free_in_buffer;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* Outputting bits to the file */
|
||||
|
||||
/* Only the right 24 bits of put_buffer are used; the valid bits are
|
||||
* left-justified in this part. At most 16 bits can be passed to emit_bits
|
||||
* in one call, and we never retain more than 7 bits in put_buffer
|
||||
* between calls, so 24 bits are sufficient.
|
||||
*/
|
||||
|
||||
INLINE
|
||||
LOCAL(boolean)
|
||||
emit_bits(working_state *state, unsigned int code, int size)
|
||||
/* Emit some bits; return TRUE if successful, FALSE if must suspend */
|
||||
{
|
||||
/* This routine is heavily used, so it's worth coding tightly. */
|
||||
register size_t put_buffer = (size_t)code;
|
||||
register int put_bits = state->cur.put_bits;
|
||||
|
||||
/* if size is 0, caller used an invalid Huffman table entry */
|
||||
if (size == 0)
|
||||
ERREXIT(state->cinfo, JERR_HUFF_MISSING_CODE);
|
||||
|
||||
put_buffer &= (((size_t)1) << size) - 1; /* mask off any extra bits in code */
|
||||
|
||||
put_bits += size; /* new number of bits in buffer */
|
||||
|
||||
put_buffer <<= 24 - put_bits; /* align incoming bits */
|
||||
|
||||
put_buffer |= state->cur.put_buffer; /* and merge with old buffer contents */
|
||||
|
||||
while (put_bits >= 8) {
|
||||
int c = (int)((put_buffer >> 16) & 0xFF);
|
||||
|
||||
emit_byte(state, c, return FALSE);
|
||||
if (c == 0xFF) { /* need to stuff a zero byte? */
|
||||
emit_byte(state, 0, return FALSE);
|
||||
}
|
||||
put_buffer <<= 8;
|
||||
put_bits -= 8;
|
||||
}
|
||||
|
||||
state->cur.put_buffer = put_buffer; /* update state variables */
|
||||
state->cur.put_bits = put_bits;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
LOCAL(boolean)
|
||||
flush_bits(working_state *state)
|
||||
{
|
||||
if (!emit_bits(state, 0x7F, 7)) /* fill any partial byte with ones */
|
||||
return FALSE;
|
||||
state->cur.put_buffer = 0; /* and reset bit-buffer to empty */
|
||||
state->cur.put_bits = 0;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Emit a restart marker & resynchronize predictions.
|
||||
*/
|
||||
|
||||
LOCAL(boolean)
|
||||
emit_restart(working_state *state, int restart_num)
|
||||
{
|
||||
if (!flush_bits(state))
|
||||
return FALSE;
|
||||
|
||||
emit_byte(state, 0xFF, return FALSE);
|
||||
emit_byte(state, JPEG_RST0 + restart_num, return FALSE);
|
||||
|
||||
/* The restart counter is not updated until we successfully write the MCU. */
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Encode and output nMCU MCUs' worth of Huffman-compressed differences.
|
||||
*/
|
||||
|
||||
METHODDEF(JDIMENSION)
|
||||
encode_mcus_huff(j_compress_ptr cinfo, JDIFFIMAGE diff_buf,
|
||||
JDIMENSION MCU_row_num, JDIMENSION MCU_col_num,
|
||||
JDIMENSION nMCU)
|
||||
{
|
||||
lhuff_entropy_ptr entropy = (lhuff_entropy_ptr)cinfo->entropy;
|
||||
working_state state;
|
||||
int sampn, ci, yoffset, MCU_width, ptrn;
|
||||
JDIMENSION mcu_num;
|
||||
|
||||
/* Load up working state */
|
||||
state.next_output_byte = cinfo->dest->next_output_byte;
|
||||
state.free_in_buffer = cinfo->dest->free_in_buffer;
|
||||
state.cur = entropy->saved;
|
||||
state.cinfo = cinfo;
|
||||
|
||||
/* Emit restart marker if needed */
|
||||
if (cinfo->restart_interval) {
|
||||
if (entropy->restarts_to_go == 0)
|
||||
if (!emit_restart(&state, entropy->next_restart_num))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set input pointer locations based on MCU_col_num */
|
||||
for (ptrn = 0; ptrn < entropy->num_input_ptrs; ptrn++) {
|
||||
ci = entropy->input_ptr_info[ptrn].ci;
|
||||
yoffset = entropy->input_ptr_info[ptrn].yoffset;
|
||||
MCU_width = entropy->input_ptr_info[ptrn].MCU_width;
|
||||
entropy->input_ptr[ptrn] =
|
||||
diff_buf[ci][MCU_row_num + yoffset] + (MCU_col_num * MCU_width);
|
||||
}
|
||||
|
||||
for (mcu_num = 0; mcu_num < nMCU; mcu_num++) {
|
||||
|
||||
/* Inner loop handles the samples in the MCU */
|
||||
for (sampn = 0; sampn < cinfo->blocks_in_MCU; sampn++) {
|
||||
register int temp, temp2;
|
||||
register int nbits;
|
||||
c_derived_tbl *dctbl = entropy->cur_tbls[sampn];
|
||||
|
||||
/* Encode the difference per section H.1.2.2 */
|
||||
|
||||
/* Input the sample difference */
|
||||
temp = *entropy->input_ptr[entropy->input_ptr_index[sampn]]++;
|
||||
|
||||
if (temp & 0x8000) { /* instead of temp < 0 */
|
||||
temp = (-temp) & 0x7FFF; /* absolute value, mod 2^16 */
|
||||
if (temp == 0) /* special case: magnitude = 32768 */
|
||||
temp2 = temp = 0x8000;
|
||||
temp2 = ~temp; /* one's complement of magnitude */
|
||||
} else {
|
||||
temp &= 0x7FFF; /* abs value mod 2^16 */
|
||||
temp2 = temp; /* magnitude */
|
||||
}
|
||||
|
||||
/* Find the number of bits needed for the magnitude of the difference */
|
||||
nbits = 0;
|
||||
while (temp) {
|
||||
nbits++;
|
||||
temp >>= 1;
|
||||
}
|
||||
/* Check for out-of-range difference values.
|
||||
*/
|
||||
if (nbits > MAX_DIFF_BITS)
|
||||
ERREXIT(cinfo, JERR_BAD_DCT_COEF);
|
||||
|
||||
/* Emit the Huffman-coded symbol for the number of bits */
|
||||
if (!emit_bits(&state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits]))
|
||||
return mcu_num;
|
||||
|
||||
/* Emit that number of bits of the value, if positive, */
|
||||
/* or the complement of its magnitude, if negative. */
|
||||
if (nbits && /* emit_bits rejects calls with size 0 */
|
||||
nbits != 16) /* special case: no bits should be emitted */
|
||||
if (!emit_bits(&state, (unsigned int)temp2, nbits))
|
||||
return mcu_num;
|
||||
}
|
||||
|
||||
/* Completed MCU, so update state */
|
||||
cinfo->dest->next_output_byte = state.next_output_byte;
|
||||
cinfo->dest->free_in_buffer = state.free_in_buffer;
|
||||
entropy->saved = state.cur;
|
||||
|
||||
/* Update restart-interval state too */
|
||||
if (cinfo->restart_interval) {
|
||||
if (entropy->restarts_to_go == 0) {
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
entropy->next_restart_num++;
|
||||
entropy->next_restart_num &= 7;
|
||||
}
|
||||
entropy->restarts_to_go--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nMCU;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Finish up at the end of a Huffman-compressed scan.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
finish_pass_huff(j_compress_ptr cinfo)
|
||||
{
|
||||
lhuff_entropy_ptr entropy = (lhuff_entropy_ptr)cinfo->entropy;
|
||||
working_state state;
|
||||
|
||||
/* Load up working state ... flush_bits needs it */
|
||||
state.next_output_byte = cinfo->dest->next_output_byte;
|
||||
state.free_in_buffer = cinfo->dest->free_in_buffer;
|
||||
state.cur = entropy->saved;
|
||||
state.cinfo = cinfo;
|
||||
|
||||
/* Flush out the last data */
|
||||
if (!flush_bits(&state))
|
||||
ERREXIT(cinfo, JERR_CANT_SUSPEND);
|
||||
|
||||
/* Update state */
|
||||
cinfo->dest->next_output_byte = state.next_output_byte;
|
||||
cinfo->dest->free_in_buffer = state.free_in_buffer;
|
||||
entropy->saved = state.cur;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Huffman coding optimization.
|
||||
*
|
||||
* We first scan the supplied data and count the number of uses of each symbol
|
||||
* that is to be Huffman-coded. (This process MUST agree with the code above.)
|
||||
* Then we build a Huffman coding tree for the observed counts.
|
||||
* Symbols which are not needed at all for the particular image are not
|
||||
* assigned any code, which saves space in the DHT marker as well as in
|
||||
* the compressed data.
|
||||
*/
|
||||
|
||||
#ifdef ENTROPY_OPT_SUPPORTED
|
||||
|
||||
/*
|
||||
* Trial-encode nMCU MCUs' worth of Huffman-compressed differences.
|
||||
* No data is actually output, so no suspension return is possible.
|
||||
*/
|
||||
|
||||
METHODDEF(JDIMENSION)
|
||||
encode_mcus_gather(j_compress_ptr cinfo, JDIFFIMAGE diff_buf,
|
||||
JDIMENSION MCU_row_num, JDIMENSION MCU_col_num,
|
||||
JDIMENSION nMCU)
|
||||
{
|
||||
lhuff_entropy_ptr entropy = (lhuff_entropy_ptr)cinfo->entropy;
|
||||
int sampn, ci, yoffset, MCU_width, ptrn;
|
||||
JDIMENSION mcu_num;
|
||||
|
||||
/* Take care of restart intervals if needed */
|
||||
if (cinfo->restart_interval) {
|
||||
if (entropy->restarts_to_go == 0) {
|
||||
/* Update restart state */
|
||||
entropy->restarts_to_go = cinfo->restart_interval;
|
||||
}
|
||||
entropy->restarts_to_go--;
|
||||
}
|
||||
|
||||
/* Set input pointer locations based on MCU_col_num */
|
||||
for (ptrn = 0; ptrn < entropy->num_input_ptrs; ptrn++) {
|
||||
ci = entropy->input_ptr_info[ptrn].ci;
|
||||
yoffset = entropy->input_ptr_info[ptrn].yoffset;
|
||||
MCU_width = entropy->input_ptr_info[ptrn].MCU_width;
|
||||
entropy->input_ptr[ptrn] =
|
||||
diff_buf[ci][MCU_row_num + yoffset] + (MCU_col_num * MCU_width);
|
||||
}
|
||||
|
||||
for (mcu_num = 0; mcu_num < nMCU; mcu_num++) {
|
||||
|
||||
/* Inner loop handles the samples in the MCU */
|
||||
for (sampn = 0; sampn < cinfo->blocks_in_MCU; sampn++) {
|
||||
register int temp;
|
||||
register int nbits;
|
||||
long *counts = entropy->cur_counts[sampn];
|
||||
|
||||
/* Encode the difference per section H.1.2.2 */
|
||||
|
||||
/* Input the sample difference */
|
||||
temp = *entropy->input_ptr[entropy->input_ptr_index[sampn]]++;
|
||||
|
||||
if (temp & 0x8000) { /* instead of temp < 0 */
|
||||
temp = (-temp) & 0x7FFF; /* absolute value, mod 2^16 */
|
||||
if (temp == 0) /* special case: magnitude = 32768 */
|
||||
temp = 0x8000;
|
||||
} else
|
||||
temp &= 0x7FFF; /* abs value mod 2^16 */
|
||||
|
||||
/* Find the number of bits needed for the magnitude of the difference */
|
||||
nbits = 0;
|
||||
while (temp) {
|
||||
nbits++;
|
||||
temp >>= 1;
|
||||
}
|
||||
/* Check for out-of-range difference values.
|
||||
*/
|
||||
if (nbits > MAX_DIFF_BITS)
|
||||
ERREXIT(cinfo, JERR_BAD_DCT_COEF);
|
||||
|
||||
/* Count the Huffman symbol for the number of bits */
|
||||
counts[nbits]++;
|
||||
}
|
||||
}
|
||||
|
||||
return nMCU;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Finish up a statistics-gathering pass and create the new Huffman tables.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
finish_pass_gather(j_compress_ptr cinfo)
|
||||
{
|
||||
lhuff_entropy_ptr entropy = (lhuff_entropy_ptr)cinfo->entropy;
|
||||
int ci, dctbl;
|
||||
jpeg_component_info *compptr;
|
||||
JHUFF_TBL **htblptr;
|
||||
boolean did_dc[NUM_HUFF_TBLS];
|
||||
|
||||
/* It's important not to apply jpeg_gen_optimal_table more than once
|
||||
* per table, because it clobbers the input frequency counts!
|
||||
*/
|
||||
memset(did_dc, 0, sizeof(did_dc));
|
||||
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
dctbl = compptr->dc_tbl_no;
|
||||
if (!did_dc[dctbl]) {
|
||||
htblptr = &cinfo->dc_huff_tbl_ptrs[dctbl];
|
||||
if (*htblptr == NULL)
|
||||
*htblptr = jpeg_alloc_huff_table((j_common_ptr)cinfo);
|
||||
jpeg_gen_optimal_table(cinfo, *htblptr, entropy->count_ptrs[dctbl]);
|
||||
did_dc[dctbl] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* ENTROPY_OPT_SUPPORTED */
|
||||
|
||||
|
||||
/*
|
||||
* Module initialization routine for Huffman entropy encoding.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jinit_lhuff_encoder(j_compress_ptr cinfo)
|
||||
{
|
||||
lhuff_entropy_ptr entropy;
|
||||
int i;
|
||||
|
||||
entropy = (lhuff_entropy_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(lhuff_entropy_encoder));
|
||||
cinfo->entropy = (struct jpeg_entropy_encoder *)entropy;
|
||||
entropy->pub.start_pass = start_pass_lhuff;
|
||||
|
||||
/* Mark tables unallocated */
|
||||
for (i = 0; i < NUM_HUFF_TBLS; i++) {
|
||||
entropy->derived_tbls[i] = NULL;
|
||||
#ifdef ENTROPY_OPT_SUPPORTED
|
||||
entropy->count_ptrs[i] = NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* C_LOSSLESS_SUPPORTED */
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* jclossls.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1998, Thomas G. Lane.
|
||||
* Lossless JPEG Modifications:
|
||||
* Copyright (C) 1999, Ken Murchison.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains prediction, sample differencing, and point transform
|
||||
* routines for the lossless JPEG compressor.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jlossls.h"
|
||||
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
|
||||
|
||||
/************************** Sample differencing **************************/
|
||||
|
||||
/*
|
||||
* In order to avoid a performance penalty for checking which predictor is
|
||||
* being used and which row is being processed for each call of the
|
||||
* undifferencer, and to promote optimization, we have separate differencing
|
||||
* functions for each predictor selection value.
|
||||
*
|
||||
* We are able to avoid duplicating source code by implementing the predictors
|
||||
* and differencers as macros. Each of the differencing functions is simply a
|
||||
* wrapper around a DIFFERENCE macro with the appropriate PREDICTOR macro
|
||||
* passed as an argument.
|
||||
*/
|
||||
|
||||
/* Forward declarations */
|
||||
LOCAL(void) reset_predictor(j_compress_ptr cinfo, int ci);
|
||||
|
||||
|
||||
/* Predictor for the first column of the first row: 2^(P-Pt-1) */
|
||||
#define INITIAL_PREDICTORx (1 << (cinfo->data_precision - cinfo->Al - 1))
|
||||
|
||||
/* Predictor for the first column of the remaining rows: Rb */
|
||||
#define INITIAL_PREDICTOR2 prev_row[0]
|
||||
|
||||
|
||||
/*
|
||||
* 1-Dimensional differencer routine.
|
||||
*
|
||||
* This macro implements the 1-D horizontal predictor (1). INITIAL_PREDICTOR
|
||||
* is used as the special case predictor for the first column, which must be
|
||||
* either INITIAL_PREDICTOR2 or INITIAL_PREDICTORx. The remaining samples
|
||||
* use PREDICTOR1.
|
||||
*/
|
||||
|
||||
#define DIFFERENCE_1D(INITIAL_PREDICTOR) \
|
||||
lossless_comp_ptr losslessc = (lossless_comp_ptr)cinfo->fdct; \
|
||||
boolean restart = FALSE; \
|
||||
int samp, Ra; \
|
||||
\
|
||||
samp = *input_buf++; \
|
||||
*diff_buf++ = samp - INITIAL_PREDICTOR; \
|
||||
\
|
||||
while (--width) { \
|
||||
Ra = samp; \
|
||||
samp = *input_buf++; \
|
||||
*diff_buf++ = samp - PREDICTOR1; \
|
||||
} \
|
||||
\
|
||||
/* Account for restart interval (no-op if not using restarts) */ \
|
||||
if (cinfo->restart_interval) { \
|
||||
if (--(losslessc->restart_rows_to_go[ci]) == 0) { \
|
||||
reset_predictor(cinfo, ci); \
|
||||
restart = TRUE; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 2-Dimensional differencer routine.
|
||||
*
|
||||
* This macro implements the 2-D horizontal predictors (#2-7). PREDICTOR2 is
|
||||
* used as the special case predictor for the first column. The remaining
|
||||
* samples use PREDICTOR, which is a function of Ra, Rb, and Rc.
|
||||
*
|
||||
* Because prev_row and output_buf may point to the same storage area (in an
|
||||
* interleaved image with Vi=1, for example), we must take care to buffer Rb/Rc
|
||||
* before writing the current reconstructed sample value into output_buf.
|
||||
*/
|
||||
|
||||
#define DIFFERENCE_2D(PREDICTOR) \
|
||||
lossless_comp_ptr losslessc = (lossless_comp_ptr)cinfo->fdct; \
|
||||
int samp, Ra, Rb, Rc; \
|
||||
\
|
||||
Rb = *prev_row++; \
|
||||
samp = *input_buf++; \
|
||||
*diff_buf++ = samp - PREDICTOR2; \
|
||||
\
|
||||
while (--width) { \
|
||||
Rc = Rb; \
|
||||
Rb = *prev_row++; \
|
||||
Ra = samp; \
|
||||
samp = *input_buf++; \
|
||||
*diff_buf++ = samp - PREDICTOR; \
|
||||
} \
|
||||
\
|
||||
/* Account for restart interval (no-op if not using restarts) */ \
|
||||
if (cinfo->restart_interval) { \
|
||||
if (--losslessc->restart_rows_to_go[ci] == 0) \
|
||||
reset_predictor(cinfo, ci); \
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Differencers for the second and subsequent rows in a scan or restart
|
||||
* interval. The first sample in the row is differenced using the vertical
|
||||
* predictor (2). The rest of the samples are differenced using the predictor
|
||||
* specified in the scan header.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
jpeg_difference1(j_compress_ptr cinfo, int ci,
|
||||
_JSAMPROW input_buf, _JSAMPROW prev_row,
|
||||
JDIFFROW diff_buf, JDIMENSION width)
|
||||
{
|
||||
DIFFERENCE_1D(INITIAL_PREDICTOR2);
|
||||
(void)(restart);
|
||||
}
|
||||
|
||||
METHODDEF(void)
|
||||
jpeg_difference2(j_compress_ptr cinfo, int ci,
|
||||
_JSAMPROW input_buf, _JSAMPROW prev_row,
|
||||
JDIFFROW diff_buf, JDIMENSION width)
|
||||
{
|
||||
DIFFERENCE_2D(PREDICTOR2);
|
||||
(void)(Ra);
|
||||
(void)(Rc);
|
||||
}
|
||||
|
||||
METHODDEF(void)
|
||||
jpeg_difference3(j_compress_ptr cinfo, int ci,
|
||||
_JSAMPROW input_buf, _JSAMPROW prev_row,
|
||||
JDIFFROW diff_buf, JDIMENSION width)
|
||||
{
|
||||
DIFFERENCE_2D(PREDICTOR3);
|
||||
(void)(Ra);
|
||||
}
|
||||
|
||||
METHODDEF(void)
|
||||
jpeg_difference4(j_compress_ptr cinfo, int ci,
|
||||
_JSAMPROW input_buf, _JSAMPROW prev_row,
|
||||
JDIFFROW diff_buf, JDIMENSION width)
|
||||
{
|
||||
DIFFERENCE_2D(PREDICTOR4);
|
||||
}
|
||||
|
||||
METHODDEF(void)
|
||||
jpeg_difference5(j_compress_ptr cinfo, int ci,
|
||||
_JSAMPROW input_buf, _JSAMPROW prev_row,
|
||||
JDIFFROW diff_buf, JDIMENSION width)
|
||||
{
|
||||
DIFFERENCE_2D(PREDICTOR5);
|
||||
}
|
||||
|
||||
METHODDEF(void)
|
||||
jpeg_difference6(j_compress_ptr cinfo, int ci,
|
||||
_JSAMPROW input_buf, _JSAMPROW prev_row,
|
||||
JDIFFROW diff_buf, JDIMENSION width)
|
||||
{
|
||||
DIFFERENCE_2D(PREDICTOR6);
|
||||
}
|
||||
|
||||
METHODDEF(void)
|
||||
jpeg_difference7(j_compress_ptr cinfo, int ci,
|
||||
_JSAMPROW input_buf, _JSAMPROW prev_row,
|
||||
JDIFFROW diff_buf, JDIMENSION width)
|
||||
{
|
||||
DIFFERENCE_2D(PREDICTOR7);
|
||||
(void)(Rc);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Differencer for the first row in a scan or restart interval. The first
|
||||
* sample in the row is differenced using the special predictor constant
|
||||
* x = 2 ^ (P-Pt-1). The rest of the samples are differenced using the
|
||||
* 1-D horizontal predictor (1).
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
jpeg_difference_first_row(j_compress_ptr cinfo, int ci,
|
||||
_JSAMPROW input_buf, _JSAMPROW prev_row,
|
||||
JDIFFROW diff_buf, JDIMENSION width)
|
||||
{
|
||||
DIFFERENCE_1D(INITIAL_PREDICTORx);
|
||||
|
||||
/*
|
||||
* Now that we have differenced the first row, we want to use the
|
||||
* differencer that corresponds to the predictor specified in the
|
||||
* scan header.
|
||||
*
|
||||
* Note that we don't do this if we have just reset the predictor
|
||||
* for a new restart interval.
|
||||
*/
|
||||
if (!restart) {
|
||||
switch (cinfo->Ss) {
|
||||
case 1:
|
||||
losslessc->predict_difference[ci] = jpeg_difference1;
|
||||
break;
|
||||
case 2:
|
||||
losslessc->predict_difference[ci] = jpeg_difference2;
|
||||
break;
|
||||
case 3:
|
||||
losslessc->predict_difference[ci] = jpeg_difference3;
|
||||
break;
|
||||
case 4:
|
||||
losslessc->predict_difference[ci] = jpeg_difference4;
|
||||
break;
|
||||
case 5:
|
||||
losslessc->predict_difference[ci] = jpeg_difference5;
|
||||
break;
|
||||
case 6:
|
||||
losslessc->predict_difference[ci] = jpeg_difference6;
|
||||
break;
|
||||
case 7:
|
||||
losslessc->predict_difference[ci] = jpeg_difference7;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset predictor at the start of a pass or restart interval.
|
||||
*/
|
||||
|
||||
LOCAL(void)
|
||||
reset_predictor(j_compress_ptr cinfo, int ci)
|
||||
{
|
||||
lossless_comp_ptr losslessc = (lossless_comp_ptr)cinfo->fdct;
|
||||
|
||||
/* Initialize restart counter */
|
||||
losslessc->restart_rows_to_go[ci] =
|
||||
cinfo->restart_interval / cinfo->MCUs_per_row;
|
||||
|
||||
/* Set difference function to first row function */
|
||||
losslessc->predict_difference[ci] = jpeg_difference_first_row;
|
||||
}
|
||||
|
||||
|
||||
/********************** Sample downscaling by 2^Pt ***********************/
|
||||
|
||||
METHODDEF(void)
|
||||
simple_downscale(j_compress_ptr cinfo,
|
||||
_JSAMPROW input_buf, _JSAMPROW output_buf, JDIMENSION width)
|
||||
{
|
||||
do {
|
||||
*output_buf++ = (_JSAMPLE)RIGHT_SHIFT(*input_buf++, cinfo->Al);
|
||||
} while (--width);
|
||||
}
|
||||
|
||||
|
||||
METHODDEF(void)
|
||||
noscale(j_compress_ptr cinfo,
|
||||
_JSAMPROW input_buf, _JSAMPROW output_buf, JDIMENSION width)
|
||||
{
|
||||
memcpy(output_buf, input_buf, width * sizeof(_JSAMPLE));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize for a processing pass.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
start_pass_lossless(j_compress_ptr cinfo)
|
||||
{
|
||||
lossless_comp_ptr losslessc = (lossless_comp_ptr)cinfo->fdct;
|
||||
int ci;
|
||||
|
||||
/* Set scaler function based on Pt */
|
||||
if (cinfo->Al)
|
||||
losslessc->scaler_scale = simple_downscale;
|
||||
else
|
||||
losslessc->scaler_scale = noscale;
|
||||
|
||||
/* Check that the restart interval is an integer multiple of the number
|
||||
* of MCUs in an MCU row.
|
||||
*/
|
||||
if (cinfo->restart_interval % cinfo->MCUs_per_row != 0)
|
||||
ERREXIT2(cinfo, JERR_BAD_RESTART,
|
||||
cinfo->restart_interval, cinfo->MCUs_per_row);
|
||||
|
||||
/* Set predictors for start of pass */
|
||||
for (ci = 0; ci < cinfo->num_components; ci++)
|
||||
reset_predictor(cinfo, ci);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the lossless compressor.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
_jinit_lossless_compressor(j_compress_ptr cinfo)
|
||||
{
|
||||
lossless_comp_ptr losslessc;
|
||||
|
||||
/* Create subobject in permanent pool */
|
||||
losslessc = (lossless_comp_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
|
||||
sizeof(jpeg_lossless_compressor));
|
||||
cinfo->fdct = (struct jpeg_forward_dct *)losslessc;
|
||||
losslessc->pub.start_pass = start_pass_lossless;
|
||||
}
|
||||
|
||||
#endif /* C_LOSSLESS_SUPPORTED */
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* jcmainct.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1994-1996, Thomas G. Lane.
|
||||
* Lossless JPEG Modifications:
|
||||
* Copyright (C) 1999, Ken Murchison.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains the main buffer controller for compression.
|
||||
* The main buffer lies between the pre-processor and the JPEG
|
||||
* compressor proper; it holds downsampled data in the JPEG colorspace.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jsamplecomp.h"
|
||||
|
||||
|
||||
#if BITS_IN_JSAMPLE != 16 || defined(C_LOSSLESS_SUPPORTED)
|
||||
|
||||
/* Private buffer controller object */
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_c_main_controller pub; /* public fields */
|
||||
|
||||
JDIMENSION cur_iMCU_row; /* number of current iMCU row */
|
||||
JDIMENSION rowgroup_ctr; /* counts row groups received in iMCU row */
|
||||
boolean suspended; /* remember if we suspended output */
|
||||
J_BUF_MODE pass_mode; /* current operating mode */
|
||||
|
||||
/* If using just a strip buffer, this points to the entire set of buffers
|
||||
* (we allocate one for each component). In the full-image case, this
|
||||
* points to the currently accessible strips of the virtual arrays.
|
||||
*/
|
||||
_JSAMPARRAY buffer[MAX_COMPONENTS];
|
||||
} my_main_controller;
|
||||
|
||||
typedef my_main_controller *my_main_ptr;
|
||||
|
||||
|
||||
/* Forward declarations */
|
||||
METHODDEF(void) process_data_simple_main(j_compress_ptr cinfo,
|
||||
_JSAMPARRAY input_buf,
|
||||
JDIMENSION *in_row_ctr,
|
||||
JDIMENSION in_rows_avail);
|
||||
|
||||
|
||||
/*
|
||||
* Initialize for a processing pass.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
start_pass_main(j_compress_ptr cinfo, J_BUF_MODE pass_mode)
|
||||
{
|
||||
my_main_ptr main_ptr = (my_main_ptr)cinfo->main;
|
||||
|
||||
/* Do nothing in raw-data mode. */
|
||||
if (cinfo->raw_data_in)
|
||||
return;
|
||||
|
||||
if (pass_mode != JBUF_PASS_THRU)
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
|
||||
main_ptr->cur_iMCU_row = 0; /* initialize counters */
|
||||
main_ptr->rowgroup_ctr = 0;
|
||||
main_ptr->suspended = FALSE;
|
||||
main_ptr->pass_mode = pass_mode; /* save mode for use by process_data */
|
||||
main_ptr->pub._process_data = process_data_simple_main;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Process some data.
|
||||
* This routine handles the simple pass-through mode,
|
||||
* where we have only a strip buffer.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
process_data_simple_main(j_compress_ptr cinfo, _JSAMPARRAY input_buf,
|
||||
JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)
|
||||
{
|
||||
my_main_ptr main_ptr = (my_main_ptr)cinfo->main;
|
||||
JDIMENSION data_unit = cinfo->master->lossless ? 1 : DCTSIZE;
|
||||
|
||||
while (main_ptr->cur_iMCU_row < cinfo->total_iMCU_rows) {
|
||||
/* Read input data if we haven't filled the main buffer yet */
|
||||
if (main_ptr->rowgroup_ctr < data_unit)
|
||||
(*cinfo->prep->_pre_process_data) (cinfo, input_buf, in_row_ctr,
|
||||
in_rows_avail, main_ptr->buffer,
|
||||
&main_ptr->rowgroup_ctr, data_unit);
|
||||
|
||||
/* If we don't have a full iMCU row buffered, return to application for
|
||||
* more data. Note that preprocessor will always pad to fill the iMCU row
|
||||
* at the bottom of the image.
|
||||
*/
|
||||
if (main_ptr->rowgroup_ctr != data_unit)
|
||||
return;
|
||||
|
||||
/* Send the completed row to the compressor */
|
||||
if (!(*cinfo->coef->_compress_data) (cinfo, main_ptr->buffer)) {
|
||||
/* If compressor did not consume the whole row, then we must need to
|
||||
* suspend processing and return to the application. In this situation
|
||||
* we pretend we didn't yet consume the last input row; otherwise, if
|
||||
* it happened to be the last row of the image, the application would
|
||||
* think we were done.
|
||||
*/
|
||||
if (!main_ptr->suspended) {
|
||||
(*in_row_ctr)--;
|
||||
main_ptr->suspended = TRUE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* We did finish the row. Undo our little suspension hack if a previous
|
||||
* call suspended; then mark the main buffer empty.
|
||||
*/
|
||||
if (main_ptr->suspended) {
|
||||
(*in_row_ctr)++;
|
||||
main_ptr->suspended = FALSE;
|
||||
}
|
||||
main_ptr->rowgroup_ctr = 0;
|
||||
main_ptr->cur_iMCU_row++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize main buffer controller.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
_jinit_c_main_controller(j_compress_ptr cinfo, boolean need_full_buffer)
|
||||
{
|
||||
my_main_ptr main_ptr;
|
||||
int ci;
|
||||
jpeg_component_info *compptr;
|
||||
int data_unit = cinfo->master->lossless ? 1 : DCTSIZE;
|
||||
|
||||
if (cinfo->data_precision != BITS_IN_JSAMPLE)
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
|
||||
main_ptr = (my_main_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(my_main_controller));
|
||||
cinfo->main = (struct jpeg_c_main_controller *)main_ptr;
|
||||
main_ptr->pub.start_pass = start_pass_main;
|
||||
|
||||
/* We don't need to create a buffer in raw-data mode. */
|
||||
if (cinfo->raw_data_in)
|
||||
return;
|
||||
|
||||
/* Create the buffer. It holds downsampled data, so each component
|
||||
* may be of a different size.
|
||||
*/
|
||||
if (need_full_buffer) {
|
||||
ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
|
||||
} else {
|
||||
/* Allocate a strip buffer for each component */
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
main_ptr->buffer[ci] = (_JSAMPARRAY)(*cinfo->mem->alloc_sarray)
|
||||
((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
compptr->width_in_blocks * data_unit,
|
||||
(JDIMENSION)(compptr->v_samp_factor * data_unit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* BITS_IN_JSAMPLE != 16 || defined(C_LOSSLESS_SUPPORTED) */
|
||||
+670
@@ -0,0 +1,670 @@
|
||||
/*
|
||||
* jcmarker.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1991-1998, Thomas G. Lane.
|
||||
* Modified 2003-2010 by Guido Vollbeding.
|
||||
* Lossless JPEG Modifications:
|
||||
* Copyright (C) 1999, Ken Murchison.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2010, 2022, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains routines to write JPEG datastream markers.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jpegapicomp.h"
|
||||
|
||||
|
||||
typedef enum { /* JPEG marker codes */
|
||||
M_SOF0 = 0xc0,
|
||||
M_SOF1 = 0xc1,
|
||||
M_SOF2 = 0xc2,
|
||||
M_SOF3 = 0xc3,
|
||||
|
||||
M_SOF5 = 0xc5,
|
||||
M_SOF6 = 0xc6,
|
||||
M_SOF7 = 0xc7,
|
||||
|
||||
M_JPG = 0xc8,
|
||||
M_SOF9 = 0xc9,
|
||||
M_SOF10 = 0xca,
|
||||
M_SOF11 = 0xcb,
|
||||
|
||||
M_SOF13 = 0xcd,
|
||||
M_SOF14 = 0xce,
|
||||
M_SOF15 = 0xcf,
|
||||
|
||||
M_DHT = 0xc4,
|
||||
|
||||
M_DAC = 0xcc,
|
||||
|
||||
M_RST0 = 0xd0,
|
||||
M_RST1 = 0xd1,
|
||||
M_RST2 = 0xd2,
|
||||
M_RST3 = 0xd3,
|
||||
M_RST4 = 0xd4,
|
||||
M_RST5 = 0xd5,
|
||||
M_RST6 = 0xd6,
|
||||
M_RST7 = 0xd7,
|
||||
|
||||
M_SOI = 0xd8,
|
||||
M_EOI = 0xd9,
|
||||
M_SOS = 0xda,
|
||||
M_DQT = 0xdb,
|
||||
M_DNL = 0xdc,
|
||||
M_DRI = 0xdd,
|
||||
M_DHP = 0xde,
|
||||
M_EXP = 0xdf,
|
||||
|
||||
M_APP0 = 0xe0,
|
||||
M_APP1 = 0xe1,
|
||||
M_APP2 = 0xe2,
|
||||
M_APP3 = 0xe3,
|
||||
M_APP4 = 0xe4,
|
||||
M_APP5 = 0xe5,
|
||||
M_APP6 = 0xe6,
|
||||
M_APP7 = 0xe7,
|
||||
M_APP8 = 0xe8,
|
||||
M_APP9 = 0xe9,
|
||||
M_APP10 = 0xea,
|
||||
M_APP11 = 0xeb,
|
||||
M_APP12 = 0xec,
|
||||
M_APP13 = 0xed,
|
||||
M_APP14 = 0xee,
|
||||
M_APP15 = 0xef,
|
||||
|
||||
M_JPG0 = 0xf0,
|
||||
M_JPG13 = 0xfd,
|
||||
M_COM = 0xfe,
|
||||
|
||||
M_TEM = 0x01,
|
||||
|
||||
M_ERROR = 0x100
|
||||
} JPEG_MARKER;
|
||||
|
||||
|
||||
/* Private state */
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_marker_writer pub; /* public fields */
|
||||
|
||||
unsigned int last_restart_interval; /* last DRI value emitted; 0 after SOI */
|
||||
} my_marker_writer;
|
||||
|
||||
typedef my_marker_writer *my_marker_ptr;
|
||||
|
||||
|
||||
/*
|
||||
* Basic output routines.
|
||||
*
|
||||
* Note that we do not support suspension while writing a marker.
|
||||
* Therefore, an application using suspension must ensure that there is
|
||||
* enough buffer space for the initial markers (typ. 600-700 bytes) before
|
||||
* calling jpeg_start_compress, and enough space to write the trailing EOI
|
||||
* (a few bytes) before calling jpeg_finish_compress. Multipass compression
|
||||
* modes are not supported at all with suspension, so those two are the only
|
||||
* points where markers will be written.
|
||||
*/
|
||||
|
||||
LOCAL(void)
|
||||
emit_byte(j_compress_ptr cinfo, int val)
|
||||
/* Emit a byte */
|
||||
{
|
||||
struct jpeg_destination_mgr *dest = cinfo->dest;
|
||||
|
||||
*(dest->next_output_byte)++ = (JOCTET)val;
|
||||
if (--dest->free_in_buffer == 0) {
|
||||
if (!(*dest->empty_output_buffer) (cinfo))
|
||||
ERREXIT(cinfo, JERR_CANT_SUSPEND);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_marker(j_compress_ptr cinfo, JPEG_MARKER mark)
|
||||
/* Emit a marker code */
|
||||
{
|
||||
emit_byte(cinfo, 0xFF);
|
||||
emit_byte(cinfo, (int)mark);
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_2bytes(j_compress_ptr cinfo, int value)
|
||||
/* Emit a 2-byte integer; these are always MSB first in JPEG files */
|
||||
{
|
||||
emit_byte(cinfo, (value >> 8) & 0xFF);
|
||||
emit_byte(cinfo, value & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Routines to write specific marker types.
|
||||
*/
|
||||
|
||||
LOCAL(int)
|
||||
emit_dqt(j_compress_ptr cinfo, int index)
|
||||
/* Emit a DQT marker */
|
||||
/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */
|
||||
{
|
||||
JQUANT_TBL *qtbl = cinfo->quant_tbl_ptrs[index];
|
||||
int prec;
|
||||
int i;
|
||||
|
||||
if (qtbl == NULL)
|
||||
ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, index);
|
||||
|
||||
prec = 0;
|
||||
for (i = 0; i < DCTSIZE2; i++) {
|
||||
if (qtbl->quantval[i] > 255)
|
||||
prec = 1;
|
||||
}
|
||||
|
||||
if (!qtbl->sent_table) {
|
||||
emit_marker(cinfo, M_DQT);
|
||||
|
||||
emit_2bytes(cinfo, prec ? DCTSIZE2 * 2 + 1 + 2 : DCTSIZE2 + 1 + 2);
|
||||
|
||||
emit_byte(cinfo, index + (prec << 4));
|
||||
|
||||
for (i = 0; i < DCTSIZE2; i++) {
|
||||
/* The table entries must be emitted in zigzag order. */
|
||||
unsigned int qval = qtbl->quantval[jpeg_natural_order[i]];
|
||||
if (prec)
|
||||
emit_byte(cinfo, (int)(qval >> 8));
|
||||
emit_byte(cinfo, (int)(qval & 0xFF));
|
||||
}
|
||||
|
||||
qtbl->sent_table = TRUE;
|
||||
}
|
||||
|
||||
return prec;
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_dht(j_compress_ptr cinfo, int index, boolean is_ac)
|
||||
/* Emit a DHT marker */
|
||||
{
|
||||
JHUFF_TBL *htbl;
|
||||
int length, i;
|
||||
|
||||
if (is_ac) {
|
||||
htbl = cinfo->ac_huff_tbl_ptrs[index];
|
||||
index += 0x10; /* output index has AC bit set */
|
||||
} else {
|
||||
htbl = cinfo->dc_huff_tbl_ptrs[index];
|
||||
}
|
||||
|
||||
if (htbl == NULL)
|
||||
ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, index);
|
||||
|
||||
if (!htbl->sent_table) {
|
||||
emit_marker(cinfo, M_DHT);
|
||||
|
||||
length = 0;
|
||||
for (i = 1; i <= 16; i++)
|
||||
length += htbl->bits[i];
|
||||
|
||||
emit_2bytes(cinfo, length + 2 + 1 + 16);
|
||||
emit_byte(cinfo, index);
|
||||
|
||||
for (i = 1; i <= 16; i++)
|
||||
emit_byte(cinfo, htbl->bits[i]);
|
||||
|
||||
for (i = 0; i < length; i++)
|
||||
emit_byte(cinfo, htbl->huffval[i]);
|
||||
|
||||
htbl->sent_table = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_dac(j_compress_ptr cinfo)
|
||||
/* Emit a DAC marker */
|
||||
/* Since the useful info is so small, we want to emit all the tables in */
|
||||
/* one DAC marker. Therefore this routine does its own scan of the table. */
|
||||
{
|
||||
#ifdef C_ARITH_CODING_SUPPORTED
|
||||
char dc_in_use[NUM_ARITH_TBLS];
|
||||
char ac_in_use[NUM_ARITH_TBLS];
|
||||
int length, i;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
for (i = 0; i < NUM_ARITH_TBLS; i++)
|
||||
dc_in_use[i] = ac_in_use[i] = 0;
|
||||
|
||||
for (i = 0; i < cinfo->comps_in_scan; i++) {
|
||||
compptr = cinfo->cur_comp_info[i];
|
||||
/* DC needs no table for refinement scan */
|
||||
if (cinfo->Ss == 0 && cinfo->Ah == 0)
|
||||
dc_in_use[compptr->dc_tbl_no] = 1;
|
||||
/* AC needs no table when not present */
|
||||
if (cinfo->Se)
|
||||
ac_in_use[compptr->ac_tbl_no] = 1;
|
||||
}
|
||||
|
||||
length = 0;
|
||||
for (i = 0; i < NUM_ARITH_TBLS; i++)
|
||||
length += dc_in_use[i] + ac_in_use[i];
|
||||
|
||||
if (length) {
|
||||
emit_marker(cinfo, M_DAC);
|
||||
|
||||
emit_2bytes(cinfo, length * 2 + 2);
|
||||
|
||||
for (i = 0; i < NUM_ARITH_TBLS; i++) {
|
||||
if (dc_in_use[i]) {
|
||||
emit_byte(cinfo, i);
|
||||
emit_byte(cinfo, cinfo->arith_dc_L[i] + (cinfo->arith_dc_U[i] << 4));
|
||||
}
|
||||
if (ac_in_use[i]) {
|
||||
emit_byte(cinfo, i + 0x10);
|
||||
emit_byte(cinfo, cinfo->arith_ac_K[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* C_ARITH_CODING_SUPPORTED */
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_dri(j_compress_ptr cinfo)
|
||||
/* Emit a DRI marker */
|
||||
{
|
||||
emit_marker(cinfo, M_DRI);
|
||||
|
||||
emit_2bytes(cinfo, 4); /* fixed length */
|
||||
|
||||
emit_2bytes(cinfo, (int)cinfo->restart_interval);
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_sof(j_compress_ptr cinfo, JPEG_MARKER code)
|
||||
/* Emit a SOF marker */
|
||||
{
|
||||
int ci;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
emit_marker(cinfo, code);
|
||||
|
||||
emit_2bytes(cinfo, 3 * cinfo->num_components + 2 + 5 + 1); /* length */
|
||||
|
||||
/* Make sure image isn't bigger than SOF field can handle */
|
||||
if ((long)cinfo->_jpeg_height > 65535L || (long)cinfo->_jpeg_width > 65535L)
|
||||
ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int)65535);
|
||||
|
||||
emit_byte(cinfo, cinfo->data_precision);
|
||||
emit_2bytes(cinfo, (int)cinfo->_jpeg_height);
|
||||
emit_2bytes(cinfo, (int)cinfo->_jpeg_width);
|
||||
|
||||
emit_byte(cinfo, cinfo->num_components);
|
||||
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
emit_byte(cinfo, compptr->component_id);
|
||||
emit_byte(cinfo, (compptr->h_samp_factor << 4) + compptr->v_samp_factor);
|
||||
emit_byte(cinfo, compptr->quant_tbl_no);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_sos(j_compress_ptr cinfo)
|
||||
/* Emit a SOS marker */
|
||||
{
|
||||
int i, td, ta;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
emit_marker(cinfo, M_SOS);
|
||||
|
||||
emit_2bytes(cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3); /* length */
|
||||
|
||||
emit_byte(cinfo, cinfo->comps_in_scan);
|
||||
|
||||
for (i = 0; i < cinfo->comps_in_scan; i++) {
|
||||
compptr = cinfo->cur_comp_info[i];
|
||||
emit_byte(cinfo, compptr->component_id);
|
||||
|
||||
/* We emit 0 for unused field(s); this is recommended by the P&M text
|
||||
* but does not seem to be specified in the standard.
|
||||
*/
|
||||
|
||||
/* DC needs no table for refinement scan */
|
||||
td = cinfo->Ss == 0 && cinfo->Ah == 0 ? compptr->dc_tbl_no : 0;
|
||||
/* AC needs no table when not present */
|
||||
ta = cinfo->Se ? compptr->ac_tbl_no : 0;
|
||||
|
||||
emit_byte(cinfo, (td << 4) + ta);
|
||||
}
|
||||
|
||||
emit_byte(cinfo, cinfo->Ss);
|
||||
emit_byte(cinfo, cinfo->Se);
|
||||
emit_byte(cinfo, (cinfo->Ah << 4) + cinfo->Al);
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_jfif_app0(j_compress_ptr cinfo)
|
||||
/* Emit a JFIF-compliant APP0 marker */
|
||||
{
|
||||
/*
|
||||
* Length of APP0 block (2 bytes)
|
||||
* Block ID (4 bytes - ASCII "JFIF")
|
||||
* Zero byte (1 byte to terminate the ID string)
|
||||
* Version Major, Minor (2 bytes - major first)
|
||||
* Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm)
|
||||
* Xdpu (2 bytes - dots per unit horizontal)
|
||||
* Ydpu (2 bytes - dots per unit vertical)
|
||||
* Thumbnail X size (1 byte)
|
||||
* Thumbnail Y size (1 byte)
|
||||
*/
|
||||
|
||||
emit_marker(cinfo, M_APP0);
|
||||
|
||||
emit_2bytes(cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); /* length */
|
||||
|
||||
emit_byte(cinfo, 0x4A); /* Identifier: ASCII "JFIF" */
|
||||
emit_byte(cinfo, 0x46);
|
||||
emit_byte(cinfo, 0x49);
|
||||
emit_byte(cinfo, 0x46);
|
||||
emit_byte(cinfo, 0);
|
||||
emit_byte(cinfo, cinfo->JFIF_major_version); /* Version fields */
|
||||
emit_byte(cinfo, cinfo->JFIF_minor_version);
|
||||
emit_byte(cinfo, cinfo->density_unit); /* Pixel size information */
|
||||
emit_2bytes(cinfo, (int)cinfo->X_density);
|
||||
emit_2bytes(cinfo, (int)cinfo->Y_density);
|
||||
emit_byte(cinfo, 0); /* No thumbnail image */
|
||||
emit_byte(cinfo, 0);
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
emit_adobe_app14(j_compress_ptr cinfo)
|
||||
/* Emit an Adobe APP14 marker */
|
||||
{
|
||||
/*
|
||||
* Length of APP14 block (2 bytes)
|
||||
* Block ID (5 bytes - ASCII "Adobe")
|
||||
* Version Number (2 bytes - currently 100)
|
||||
* Flags0 (2 bytes - currently 0)
|
||||
* Flags1 (2 bytes - currently 0)
|
||||
* Color transform (1 byte)
|
||||
*
|
||||
* Although Adobe TN 5116 mentions Version = 101, all the Adobe files
|
||||
* now in circulation seem to use Version = 100, so that's what we write.
|
||||
*
|
||||
* We write the color transform byte as 1 if the JPEG color space is
|
||||
* YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with
|
||||
* whether the encoder performed a transformation, which is pretty useless.
|
||||
*/
|
||||
|
||||
emit_marker(cinfo, M_APP14);
|
||||
|
||||
emit_2bytes(cinfo, 2 + 5 + 2 + 2 + 2 + 1); /* length */
|
||||
|
||||
emit_byte(cinfo, 0x41); /* Identifier: ASCII "Adobe" */
|
||||
emit_byte(cinfo, 0x64);
|
||||
emit_byte(cinfo, 0x6F);
|
||||
emit_byte(cinfo, 0x62);
|
||||
emit_byte(cinfo, 0x65);
|
||||
emit_2bytes(cinfo, 100); /* Version */
|
||||
emit_2bytes(cinfo, 0); /* Flags0 */
|
||||
emit_2bytes(cinfo, 0); /* Flags1 */
|
||||
switch (cinfo->jpeg_color_space) {
|
||||
case JCS_YCbCr:
|
||||
emit_byte(cinfo, 1); /* Color transform = 1 */
|
||||
break;
|
||||
case JCS_YCCK:
|
||||
emit_byte(cinfo, 2); /* Color transform = 2 */
|
||||
break;
|
||||
default:
|
||||
emit_byte(cinfo, 0); /* Color transform = 0 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* These routines allow writing an arbitrary marker with parameters.
|
||||
* The only intended use is to emit COM or APPn markers after calling
|
||||
* write_file_header and before calling write_frame_header.
|
||||
* Other uses are not guaranteed to produce desirable results.
|
||||
* Counting the parameter bytes properly is the caller's responsibility.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
write_marker_header(j_compress_ptr cinfo, int marker, unsigned int datalen)
|
||||
/* Emit an arbitrary marker header */
|
||||
{
|
||||
if (datalen > (unsigned int)65533) /* safety check */
|
||||
ERREXIT(cinfo, JERR_BAD_LENGTH);
|
||||
|
||||
emit_marker(cinfo, (JPEG_MARKER)marker);
|
||||
|
||||
emit_2bytes(cinfo, (int)(datalen + 2)); /* total length */
|
||||
}
|
||||
|
||||
METHODDEF(void)
|
||||
write_marker_byte(j_compress_ptr cinfo, int val)
|
||||
/* Emit one byte of marker parameters following write_marker_header */
|
||||
{
|
||||
emit_byte(cinfo, val);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write datastream header.
|
||||
* This consists of an SOI and optional APPn markers.
|
||||
* We recommend use of the JFIF marker, but not the Adobe marker,
|
||||
* when using YCbCr or grayscale data. The JFIF marker should NOT
|
||||
* be used for any other JPEG colorspace. The Adobe marker is helpful
|
||||
* to distinguish RGB, CMYK, and YCCK colorspaces.
|
||||
* Note that an application can write additional header markers after
|
||||
* jpeg_start_compress returns.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
write_file_header(j_compress_ptr cinfo)
|
||||
{
|
||||
my_marker_ptr marker = (my_marker_ptr)cinfo->marker;
|
||||
|
||||
emit_marker(cinfo, M_SOI); /* first the SOI */
|
||||
|
||||
/* SOI is defined to reset restart interval to 0 */
|
||||
marker->last_restart_interval = 0;
|
||||
|
||||
if (cinfo->write_JFIF_header) /* next an optional JFIF APP0 */
|
||||
emit_jfif_app0(cinfo);
|
||||
if (cinfo->write_Adobe_marker) /* next an optional Adobe APP14 */
|
||||
emit_adobe_app14(cinfo);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write frame header.
|
||||
* This consists of DQT and SOFn markers.
|
||||
* Note that we do not emit the SOF until we have emitted the DQT(s).
|
||||
* This avoids compatibility problems with incorrect implementations that
|
||||
* try to error-check the quant table numbers as soon as they see the SOF.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
write_frame_header(j_compress_ptr cinfo)
|
||||
{
|
||||
int ci, prec = 0;
|
||||
boolean is_baseline;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
if (!cinfo->master->lossless) {
|
||||
/* Emit DQT for each quantization table.
|
||||
* Note that emit_dqt() suppresses any duplicate tables.
|
||||
*/
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
prec += emit_dqt(cinfo, compptr->quant_tbl_no);
|
||||
}
|
||||
/* now prec is nonzero iff there are any 16-bit quant tables. */
|
||||
}
|
||||
|
||||
/* Check for a non-baseline specification.
|
||||
* Note we assume that Huffman table numbers won't be changed later.
|
||||
*/
|
||||
if (cinfo->arith_code || cinfo->progressive_mode ||
|
||||
cinfo->master->lossless || cinfo->data_precision != 8) {
|
||||
is_baseline = FALSE;
|
||||
} else {
|
||||
is_baseline = TRUE;
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
if (compptr->dc_tbl_no > 1 || compptr->ac_tbl_no > 1)
|
||||
is_baseline = FALSE;
|
||||
}
|
||||
if (prec && is_baseline) {
|
||||
is_baseline = FALSE;
|
||||
/* If it's baseline except for quantizer size, warn the user */
|
||||
TRACEMS(cinfo, 0, JTRC_16BIT_TABLES);
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit the proper SOF marker */
|
||||
if (cinfo->arith_code) {
|
||||
if (cinfo->progressive_mode)
|
||||
emit_sof(cinfo, M_SOF10); /* SOF code for progressive arithmetic */
|
||||
else
|
||||
emit_sof(cinfo, M_SOF9); /* SOF code for sequential arithmetic */
|
||||
} else {
|
||||
if (cinfo->progressive_mode)
|
||||
emit_sof(cinfo, M_SOF2); /* SOF code for progressive Huffman */
|
||||
else if (cinfo->master->lossless)
|
||||
emit_sof(cinfo, M_SOF3); /* SOF code for lossless Huffman */
|
||||
else if (is_baseline)
|
||||
emit_sof(cinfo, M_SOF0); /* SOF code for baseline implementation */
|
||||
else
|
||||
emit_sof(cinfo, M_SOF1); /* SOF code for non-baseline Huffman file */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write scan header.
|
||||
* This consists of DHT or DAC markers, optional DRI, and SOS.
|
||||
* Compressed data will be written following the SOS.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
write_scan_header(j_compress_ptr cinfo)
|
||||
{
|
||||
my_marker_ptr marker = (my_marker_ptr)cinfo->marker;
|
||||
int i;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
if (cinfo->arith_code) {
|
||||
/* Emit arith conditioning info. We may have some duplication
|
||||
* if the file has multiple scans, but it's so small it's hardly
|
||||
* worth worrying about.
|
||||
*/
|
||||
emit_dac(cinfo);
|
||||
} else {
|
||||
/* Emit Huffman tables.
|
||||
* Note that emit_dht() suppresses any duplicate tables.
|
||||
*/
|
||||
for (i = 0; i < cinfo->comps_in_scan; i++) {
|
||||
compptr = cinfo->cur_comp_info[i];
|
||||
/* DC needs no table for refinement scan */
|
||||
if ((cinfo->Ss == 0 && cinfo->Ah == 0) || cinfo->master->lossless)
|
||||
emit_dht(cinfo, compptr->dc_tbl_no, FALSE);
|
||||
/* AC needs no table when not present, and lossless mode uses only DC
|
||||
tables. */
|
||||
if (cinfo->Se && !cinfo->master->lossless)
|
||||
emit_dht(cinfo, compptr->ac_tbl_no, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit DRI if required --- note that DRI value could change for each scan.
|
||||
* We avoid wasting space with unnecessary DRIs, however.
|
||||
*/
|
||||
if (cinfo->restart_interval != marker->last_restart_interval) {
|
||||
emit_dri(cinfo);
|
||||
marker->last_restart_interval = cinfo->restart_interval;
|
||||
}
|
||||
|
||||
emit_sos(cinfo);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write datastream trailer.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
write_file_trailer(j_compress_ptr cinfo)
|
||||
{
|
||||
emit_marker(cinfo, M_EOI);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write an abbreviated table-specification datastream.
|
||||
* This consists of SOI, DQT and DHT tables, and EOI.
|
||||
* Any table that is defined and not marked sent_table = TRUE will be
|
||||
* emitted. Note that all tables will be marked sent_table = TRUE at exit.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
write_tables_only(j_compress_ptr cinfo)
|
||||
{
|
||||
int i;
|
||||
|
||||
emit_marker(cinfo, M_SOI);
|
||||
|
||||
for (i = 0; i < NUM_QUANT_TBLS; i++) {
|
||||
if (cinfo->quant_tbl_ptrs[i] != NULL)
|
||||
(void)emit_dqt(cinfo, i);
|
||||
}
|
||||
|
||||
if (!cinfo->arith_code) {
|
||||
for (i = 0; i < NUM_HUFF_TBLS; i++) {
|
||||
if (cinfo->dc_huff_tbl_ptrs[i] != NULL)
|
||||
emit_dht(cinfo, i, FALSE);
|
||||
if (cinfo->ac_huff_tbl_ptrs[i] != NULL)
|
||||
emit_dht(cinfo, i, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
emit_marker(cinfo, M_EOI);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the marker writer module.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jinit_marker_writer(j_compress_ptr cinfo)
|
||||
{
|
||||
my_marker_ptr marker;
|
||||
|
||||
/* Create the subobject */
|
||||
marker = (my_marker_ptr)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE,
|
||||
sizeof(my_marker_writer));
|
||||
cinfo->marker = (struct jpeg_marker_writer *)marker;
|
||||
/* Initialize method pointers */
|
||||
marker->pub.write_file_header = write_file_header;
|
||||
marker->pub.write_frame_header = write_frame_header;
|
||||
marker->pub.write_scan_header = write_scan_header;
|
||||
marker->pub.write_file_trailer = write_file_trailer;
|
||||
marker->pub.write_tables_only = write_tables_only;
|
||||
marker->pub.write_marker_header = write_marker_header;
|
||||
marker->pub.write_marker_byte = write_marker_byte;
|
||||
/* Initialize private state */
|
||||
marker->last_restart_interval = 0;
|
||||
}
|
||||
+793
@@ -0,0 +1,793 @@
|
||||
/*
|
||||
* jcmaster.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1991-1997, Thomas G. Lane.
|
||||
* Modified 2003-2010 by Guido Vollbeding.
|
||||
* Lossless JPEG Modifications:
|
||||
* Copyright (C) 1999, Ken Murchison.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2010, 2016, 2018, 2022-2024, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains master control logic for the JPEG compressor.
|
||||
* These routines are concerned with parameter validation, initial setup,
|
||||
* and inter-pass control (determining the number of passes and the work
|
||||
* to be done in each pass).
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
#include "jpegapicomp.h"
|
||||
#include "jcmaster.h"
|
||||
|
||||
|
||||
/*
|
||||
* Support routines that do various essential calculations.
|
||||
*/
|
||||
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
/*
|
||||
* Compute JPEG image dimensions and related values.
|
||||
* NOTE: this is exported for possible use by application.
|
||||
* Hence it mustn't do anything that can't be done twice.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_calc_jpeg_dimensions(j_compress_ptr cinfo)
|
||||
/* Do computations that are needed before master selection phase */
|
||||
{
|
||||
int data_unit = cinfo->master->lossless ? 1 : DCTSIZE;
|
||||
|
||||
/* Hardwire it to "no scaling" */
|
||||
cinfo->jpeg_width = cinfo->image_width;
|
||||
cinfo->jpeg_height = cinfo->image_height;
|
||||
cinfo->min_DCT_h_scaled_size = data_unit;
|
||||
cinfo->min_DCT_v_scaled_size = data_unit;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
LOCAL(boolean)
|
||||
using_std_huff_tables(j_compress_ptr cinfo)
|
||||
{
|
||||
int i;
|
||||
|
||||
static const UINT8 bits_dc_luminance[17] = {
|
||||
/* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
static const UINT8 val_dc_luminance[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
|
||||
};
|
||||
|
||||
static const UINT8 bits_dc_chrominance[17] = {
|
||||
/* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
|
||||
};
|
||||
static const UINT8 val_dc_chrominance[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
|
||||
};
|
||||
|
||||
static const UINT8 bits_ac_luminance[17] = {
|
||||
/* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d
|
||||
};
|
||||
static const UINT8 val_ac_luminance[] = {
|
||||
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
|
||||
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
|
||||
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
|
||||
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
|
||||
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
|
||||
0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
|
||||
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
||||
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
|
||||
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
||||
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
|
||||
0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
||||
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
|
||||
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
|
||||
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
|
||||
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
|
||||
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
|
||||
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
|
||||
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
|
||||
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa
|
||||
};
|
||||
|
||||
static const UINT8 bits_ac_chrominance[17] = {
|
||||
/* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77
|
||||
};
|
||||
static const UINT8 val_ac_chrominance[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
|
||||
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
|
||||
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
|
||||
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
|
||||
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
|
||||
0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
|
||||
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
||||
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
|
||||
0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
|
||||
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
|
||||
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
|
||||
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
|
||||
0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
|
||||
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
|
||||
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa
|
||||
};
|
||||
|
||||
if (cinfo->dc_huff_tbl_ptrs[0] == NULL ||
|
||||
cinfo->ac_huff_tbl_ptrs[0] == NULL ||
|
||||
cinfo->dc_huff_tbl_ptrs[1] == NULL ||
|
||||
cinfo->ac_huff_tbl_ptrs[1] == NULL)
|
||||
return FALSE;
|
||||
|
||||
for (i = 2; i < NUM_HUFF_TBLS; i++) {
|
||||
if (cinfo->dc_huff_tbl_ptrs[i] != NULL ||
|
||||
cinfo->ac_huff_tbl_ptrs[i] != NULL)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (memcmp(cinfo->dc_huff_tbl_ptrs[0]->bits, bits_dc_luminance,
|
||||
sizeof(bits_dc_luminance)) ||
|
||||
memcmp(cinfo->dc_huff_tbl_ptrs[0]->huffval, val_dc_luminance,
|
||||
sizeof(val_dc_luminance)) ||
|
||||
memcmp(cinfo->ac_huff_tbl_ptrs[0]->bits, bits_ac_luminance,
|
||||
sizeof(bits_ac_luminance)) ||
|
||||
memcmp(cinfo->ac_huff_tbl_ptrs[0]->huffval, val_ac_luminance,
|
||||
sizeof(val_ac_luminance)) ||
|
||||
memcmp(cinfo->dc_huff_tbl_ptrs[1]->bits, bits_dc_chrominance,
|
||||
sizeof(bits_dc_chrominance)) ||
|
||||
memcmp(cinfo->dc_huff_tbl_ptrs[1]->huffval, val_dc_chrominance,
|
||||
sizeof(val_dc_chrominance)) ||
|
||||
memcmp(cinfo->ac_huff_tbl_ptrs[1]->bits, bits_ac_chrominance,
|
||||
sizeof(bits_ac_chrominance)) ||
|
||||
memcmp(cinfo->ac_huff_tbl_ptrs[1]->huffval, val_ac_chrominance,
|
||||
sizeof(val_ac_chrominance)))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
initial_setup(j_compress_ptr cinfo, boolean transcode_only)
|
||||
/* Do computations that are needed before master selection phase */
|
||||
{
|
||||
int ci;
|
||||
jpeg_component_info *compptr;
|
||||
long samplesperrow;
|
||||
JDIMENSION jd_samplesperrow;
|
||||
int data_unit = cinfo->master->lossless ? 1 : DCTSIZE;
|
||||
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
#if JPEG_LIB_VERSION >= 80
|
||||
if (!transcode_only)
|
||||
#endif
|
||||
jpeg_calc_jpeg_dimensions(cinfo);
|
||||
#endif
|
||||
|
||||
/* Sanity check on image dimensions */
|
||||
if (cinfo->_jpeg_height <= 0 || cinfo->_jpeg_width <= 0 ||
|
||||
cinfo->num_components <= 0 || cinfo->input_components <= 0)
|
||||
ERREXIT(cinfo, JERR_EMPTY_IMAGE);
|
||||
|
||||
/* Make sure image isn't bigger than I can handle */
|
||||
if ((long)cinfo->_jpeg_height > (long)JPEG_MAX_DIMENSION ||
|
||||
(long)cinfo->_jpeg_width > (long)JPEG_MAX_DIMENSION)
|
||||
ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int)JPEG_MAX_DIMENSION);
|
||||
|
||||
/* Width of an input scanline must be representable as JDIMENSION. */
|
||||
samplesperrow = (long)cinfo->image_width * (long)cinfo->input_components;
|
||||
jd_samplesperrow = (JDIMENSION)samplesperrow;
|
||||
if ((long)jd_samplesperrow != samplesperrow)
|
||||
ERREXIT(cinfo, JERR_WIDTH_OVERFLOW);
|
||||
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
if (cinfo->data_precision != 8 && cinfo->data_precision != 12 &&
|
||||
cinfo->data_precision != 16)
|
||||
#else
|
||||
if (cinfo->data_precision != 8 && cinfo->data_precision != 12)
|
||||
#endif
|
||||
ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision);
|
||||
|
||||
/* Check that number of components won't exceed internal array sizes */
|
||||
if (cinfo->num_components > MAX_COMPONENTS)
|
||||
ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components,
|
||||
MAX_COMPONENTS);
|
||||
|
||||
/* Compute maximum sampling factors; check factor validity */
|
||||
cinfo->max_h_samp_factor = 1;
|
||||
cinfo->max_v_samp_factor = 1;
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
if (compptr->h_samp_factor <= 0 ||
|
||||
compptr->h_samp_factor > MAX_SAMP_FACTOR ||
|
||||
compptr->v_samp_factor <= 0 ||
|
||||
compptr->v_samp_factor > MAX_SAMP_FACTOR)
|
||||
ERREXIT(cinfo, JERR_BAD_SAMPLING);
|
||||
cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor,
|
||||
compptr->h_samp_factor);
|
||||
cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor,
|
||||
compptr->v_samp_factor);
|
||||
}
|
||||
|
||||
/* Compute dimensions of components */
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++) {
|
||||
/* Fill in the correct component_index value; don't rely on application */
|
||||
compptr->component_index = ci;
|
||||
/* For compression, we never do DCT scaling. */
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
compptr->DCT_h_scaled_size = compptr->DCT_v_scaled_size = data_unit;
|
||||
#else
|
||||
compptr->DCT_scaled_size = data_unit;
|
||||
#endif
|
||||
/* Size in data units */
|
||||
compptr->width_in_blocks = (JDIMENSION)
|
||||
jdiv_round_up((long)cinfo->_jpeg_width * (long)compptr->h_samp_factor,
|
||||
(long)(cinfo->max_h_samp_factor * data_unit));
|
||||
compptr->height_in_blocks = (JDIMENSION)
|
||||
jdiv_round_up((long)cinfo->_jpeg_height * (long)compptr->v_samp_factor,
|
||||
(long)(cinfo->max_v_samp_factor * data_unit));
|
||||
/* Size in samples */
|
||||
compptr->downsampled_width = (JDIMENSION)
|
||||
jdiv_round_up((long)cinfo->_jpeg_width * (long)compptr->h_samp_factor,
|
||||
(long)cinfo->max_h_samp_factor);
|
||||
compptr->downsampled_height = (JDIMENSION)
|
||||
jdiv_round_up((long)cinfo->_jpeg_height * (long)compptr->v_samp_factor,
|
||||
(long)cinfo->max_v_samp_factor);
|
||||
/* Mark component needed (this flag isn't actually used for compression) */
|
||||
compptr->component_needed = TRUE;
|
||||
}
|
||||
|
||||
/* Compute number of fully interleaved MCU rows (number of times that
|
||||
* main controller will call coefficient or difference controller).
|
||||
*/
|
||||
cinfo->total_iMCU_rows = (JDIMENSION)
|
||||
jdiv_round_up((long)cinfo->_jpeg_height,
|
||||
(long)(cinfo->max_v_samp_factor * data_unit));
|
||||
}
|
||||
|
||||
|
||||
#if defined(C_MULTISCAN_FILES_SUPPORTED) || defined(C_LOSSLESS_SUPPORTED)
|
||||
#define NEED_SCAN_SCRIPT
|
||||
#endif
|
||||
|
||||
#ifdef NEED_SCAN_SCRIPT
|
||||
|
||||
LOCAL(void)
|
||||
validate_script(j_compress_ptr cinfo)
|
||||
/* Verify that the scan script in cinfo->scan_info[] is valid; also
|
||||
* determine whether it uses progressive JPEG, and set cinfo->progressive_mode.
|
||||
*/
|
||||
{
|
||||
const jpeg_scan_info *scanptr;
|
||||
int scanno, ncomps, ci, coefi, thisi;
|
||||
int Ss, Se, Ah, Al;
|
||||
boolean component_sent[MAX_COMPONENTS];
|
||||
#ifdef C_PROGRESSIVE_SUPPORTED
|
||||
int *last_bitpos_ptr;
|
||||
int last_bitpos[MAX_COMPONENTS][DCTSIZE2];
|
||||
/* -1 until that coefficient has been seen; then last Al for it */
|
||||
#endif
|
||||
|
||||
if (cinfo->num_scans <= 0)
|
||||
ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, 0);
|
||||
|
||||
#ifndef C_MULTISCAN_FILES_SUPPORTED
|
||||
if (cinfo->num_scans > 1)
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
#endif
|
||||
|
||||
scanptr = cinfo->scan_info;
|
||||
if (scanptr->Ss != 0 && scanptr->Se == 0) {
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
cinfo->master->lossless = TRUE;
|
||||
cinfo->progressive_mode = FALSE;
|
||||
for (ci = 0; ci < cinfo->num_components; ci++)
|
||||
component_sent[ci] = FALSE;
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
#endif
|
||||
}
|
||||
/* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1;
|
||||
* for progressive JPEG, no scan can have this.
|
||||
*/
|
||||
else if (scanptr->Ss != 0 || scanptr->Se != DCTSIZE2 - 1) {
|
||||
#ifdef C_PROGRESSIVE_SUPPORTED
|
||||
cinfo->progressive_mode = TRUE;
|
||||
cinfo->master->lossless = FALSE;
|
||||
last_bitpos_ptr = &last_bitpos[0][0];
|
||||
for (ci = 0; ci < cinfo->num_components; ci++)
|
||||
for (coefi = 0; coefi < DCTSIZE2; coefi++)
|
||||
*last_bitpos_ptr++ = -1;
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
#endif
|
||||
} else {
|
||||
cinfo->progressive_mode = cinfo->master->lossless = FALSE;
|
||||
for (ci = 0; ci < cinfo->num_components; ci++)
|
||||
component_sent[ci] = FALSE;
|
||||
}
|
||||
|
||||
for (scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++) {
|
||||
/* Validate component indexes */
|
||||
ncomps = scanptr->comps_in_scan;
|
||||
if (ncomps <= 0 || ncomps > MAX_COMPS_IN_SCAN)
|
||||
ERREXIT2(cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN);
|
||||
for (ci = 0; ci < ncomps; ci++) {
|
||||
thisi = scanptr->component_index[ci];
|
||||
if (thisi < 0 || thisi >= cinfo->num_components)
|
||||
ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno);
|
||||
/* Components must appear in SOF order within each scan */
|
||||
if (ci > 0 && thisi <= scanptr->component_index[ci - 1])
|
||||
ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno);
|
||||
}
|
||||
/* Validate progression parameters */
|
||||
Ss = scanptr->Ss;
|
||||
Se = scanptr->Se;
|
||||
Ah = scanptr->Ah;
|
||||
Al = scanptr->Al;
|
||||
if (cinfo->progressive_mode) {
|
||||
#ifdef C_PROGRESSIVE_SUPPORTED
|
||||
/* Rec. ITU-T T.81 | ISO/IEC 10918-1 simply gives the ranges 0..13 for Ah
|
||||
* and Al, but that seems wrong: the upper bound ought to depend on data
|
||||
* precision. Perhaps they really meant 0..N+1 for N-bit precision.
|
||||
* Here we allow 0..10 for 8-bit data; Al larger than 10 results in
|
||||
* out-of-range reconstructed DC values during the first DC scan,
|
||||
* which might cause problems for some decoders.
|
||||
*/
|
||||
int max_Ah_Al = cinfo->data_precision == 12 ? 13 : 10;
|
||||
|
||||
if (Ss < 0 || Ss >= DCTSIZE2 || Se < Ss || Se >= DCTSIZE2 ||
|
||||
Ah < 0 || Ah > max_Ah_Al || Al < 0 || Al > max_Ah_Al)
|
||||
ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
|
||||
if (Ss == 0) {
|
||||
if (Se != 0) /* DC and AC together not OK */
|
||||
ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
|
||||
} else {
|
||||
if (ncomps != 1) /* AC scans must be for only one component */
|
||||
ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
|
||||
}
|
||||
for (ci = 0; ci < ncomps; ci++) {
|
||||
last_bitpos_ptr = &last_bitpos[scanptr->component_index[ci]][0];
|
||||
if (Ss != 0 && last_bitpos_ptr[0] < 0) /* AC without prior DC scan */
|
||||
ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
|
||||
for (coefi = Ss; coefi <= Se; coefi++) {
|
||||
if (last_bitpos_ptr[coefi] < 0) {
|
||||
/* first scan of this coefficient */
|
||||
if (Ah != 0)
|
||||
ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
|
||||
} else {
|
||||
/* not first scan */
|
||||
if (Ah != last_bitpos_ptr[coefi] || Al != Ah - 1)
|
||||
ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
|
||||
}
|
||||
last_bitpos_ptr[coefi] = Al;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#ifdef C_LOSSLESS_SUPPORTED
|
||||
if (cinfo->master->lossless) {
|
||||
/* The JPEG spec simply gives the range 0..15 for Al (Pt), but that
|
||||
* seems wrong: the upper bound ought to depend on data precision.
|
||||
* Perhaps they really meant 0..N-1 for N-bit precision, which is what
|
||||
* we allow here. Values greater than or equal to the data precision
|
||||
* will result in a blank image.
|
||||
*/
|
||||
if (Ss < 1 || Ss > 7 || /* predictor selection value */
|
||||
Se != 0 || Ah != 0 ||
|
||||
Al < 0 || Al >= cinfo->data_precision) /* point transform */
|
||||
ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
/* For sequential JPEG, all progression parameters must be these: */
|
||||
if (Ss != 0 || Se != DCTSIZE2 - 1 || Ah != 0 || Al != 0)
|
||||
ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno);
|
||||
}
|
||||
/* Make sure components are not sent twice */
|
||||
for (ci = 0; ci < ncomps; ci++) {
|
||||
thisi = scanptr->component_index[ci];
|
||||
if (component_sent[thisi])
|
||||
ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno);
|
||||
component_sent[thisi] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Now verify that everything got sent. */
|
||||
if (cinfo->progressive_mode) {
|
||||
#ifdef C_PROGRESSIVE_SUPPORTED
|
||||
/* For progressive mode, we only check that at least some DC data
|
||||
* got sent for each component; the spec does not require that all bits
|
||||
* of all coefficients be transmitted. Would it be wiser to enforce
|
||||
* transmission of all coefficient bits??
|
||||
*/
|
||||
for (ci = 0; ci < cinfo->num_components; ci++) {
|
||||
if (last_bitpos[ci][0] < 0)
|
||||
ERREXIT(cinfo, JERR_MISSING_DATA);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
for (ci = 0; ci < cinfo->num_components; ci++) {
|
||||
if (!component_sent[ci])
|
||||
ERREXIT(cinfo, JERR_MISSING_DATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* NEED_SCAN_SCRIPT */
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
select_scan_parameters(j_compress_ptr cinfo)
|
||||
/* Set up the scan parameters for the current scan */
|
||||
{
|
||||
int ci;
|
||||
|
||||
#ifdef NEED_SCAN_SCRIPT
|
||||
if (cinfo->scan_info != NULL) {
|
||||
/* Prepare for current scan --- the script is already validated */
|
||||
my_master_ptr master = (my_master_ptr)cinfo->master;
|
||||
const jpeg_scan_info *scanptr = cinfo->scan_info + master->scan_number;
|
||||
|
||||
cinfo->comps_in_scan = scanptr->comps_in_scan;
|
||||
for (ci = 0; ci < scanptr->comps_in_scan; ci++) {
|
||||
cinfo->cur_comp_info[ci] =
|
||||
&cinfo->comp_info[scanptr->component_index[ci]];
|
||||
}
|
||||
cinfo->Ss = scanptr->Ss;
|
||||
cinfo->Se = scanptr->Se;
|
||||
cinfo->Ah = scanptr->Ah;
|
||||
cinfo->Al = scanptr->Al;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
/* Prepare for single sequential-JPEG scan containing all components */
|
||||
if (cinfo->num_components > MAX_COMPS_IN_SCAN)
|
||||
ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components,
|
||||
MAX_COMPS_IN_SCAN);
|
||||
cinfo->comps_in_scan = cinfo->num_components;
|
||||
for (ci = 0; ci < cinfo->num_components; ci++) {
|
||||
cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci];
|
||||
}
|
||||
if (!cinfo->master->lossless) {
|
||||
cinfo->Ss = 0;
|
||||
cinfo->Se = DCTSIZE2 - 1;
|
||||
cinfo->Ah = 0;
|
||||
cinfo->Al = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LOCAL(void)
|
||||
per_scan_setup(j_compress_ptr cinfo)
|
||||
/* Do computations that are needed before processing a JPEG scan */
|
||||
/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */
|
||||
{
|
||||
int ci, mcublks, tmp;
|
||||
jpeg_component_info *compptr;
|
||||
int data_unit = cinfo->master->lossless ? 1 : DCTSIZE;
|
||||
|
||||
if (cinfo->comps_in_scan == 1) {
|
||||
|
||||
/* Noninterleaved (single-component) scan */
|
||||
compptr = cinfo->cur_comp_info[0];
|
||||
|
||||
/* Overall image size in MCUs */
|
||||
cinfo->MCUs_per_row = compptr->width_in_blocks;
|
||||
cinfo->MCU_rows_in_scan = compptr->height_in_blocks;
|
||||
|
||||
/* For noninterleaved scan, always one block per MCU */
|
||||
compptr->MCU_width = 1;
|
||||
compptr->MCU_height = 1;
|
||||
compptr->MCU_blocks = 1;
|
||||
compptr->MCU_sample_width = data_unit;
|
||||
compptr->last_col_width = 1;
|
||||
/* For noninterleaved scans, it is convenient to define last_row_height
|
||||
* as the number of block rows present in the last iMCU row.
|
||||
*/
|
||||
tmp = (int)(compptr->height_in_blocks % compptr->v_samp_factor);
|
||||
if (tmp == 0) tmp = compptr->v_samp_factor;
|
||||
compptr->last_row_height = tmp;
|
||||
|
||||
/* Prepare array describing MCU composition */
|
||||
cinfo->blocks_in_MCU = 1;
|
||||
cinfo->MCU_membership[0] = 0;
|
||||
|
||||
} else {
|
||||
|
||||
/* Interleaved (multi-component) scan */
|
||||
if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN)
|
||||
ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan,
|
||||
MAX_COMPS_IN_SCAN);
|
||||
|
||||
/* Overall image size in MCUs */
|
||||
cinfo->MCUs_per_row = (JDIMENSION)
|
||||
jdiv_round_up((long)cinfo->_jpeg_width,
|
||||
(long)(cinfo->max_h_samp_factor * data_unit));
|
||||
cinfo->MCU_rows_in_scan = (JDIMENSION)
|
||||
jdiv_round_up((long)cinfo->_jpeg_height,
|
||||
(long)(cinfo->max_v_samp_factor * data_unit));
|
||||
|
||||
cinfo->blocks_in_MCU = 0;
|
||||
|
||||
for (ci = 0; ci < cinfo->comps_in_scan; ci++) {
|
||||
compptr = cinfo->cur_comp_info[ci];
|
||||
/* Sampling factors give # of blocks of component in each MCU */
|
||||
compptr->MCU_width = compptr->h_samp_factor;
|
||||
compptr->MCU_height = compptr->v_samp_factor;
|
||||
compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height;
|
||||
compptr->MCU_sample_width = compptr->MCU_width * data_unit;
|
||||
/* Figure number of non-dummy blocks in last MCU column & row */
|
||||
tmp = (int)(compptr->width_in_blocks % compptr->MCU_width);
|
||||
if (tmp == 0) tmp = compptr->MCU_width;
|
||||
compptr->last_col_width = tmp;
|
||||
tmp = (int)(compptr->height_in_blocks % compptr->MCU_height);
|
||||
if (tmp == 0) tmp = compptr->MCU_height;
|
||||
compptr->last_row_height = tmp;
|
||||
/* Prepare array describing MCU composition */
|
||||
mcublks = compptr->MCU_blocks;
|
||||
if (cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU)
|
||||
ERREXIT(cinfo, JERR_BAD_MCU_SIZE);
|
||||
while (mcublks-- > 0) {
|
||||
cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Convert restart specified in rows to actual MCU count. */
|
||||
/* Note that count must fit in 16 bits, so we provide limiting. */
|
||||
if (cinfo->restart_in_rows > 0) {
|
||||
long nominal = (long)cinfo->restart_in_rows * (long)cinfo->MCUs_per_row;
|
||||
cinfo->restart_interval = (unsigned int)MIN(nominal, 65535L);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Per-pass setup.
|
||||
* This is called at the beginning of each pass. We determine which modules
|
||||
* will be active during this pass and give them appropriate start_pass calls.
|
||||
* We also set is_last_pass to indicate whether any more passes will be
|
||||
* required.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
prepare_for_pass(j_compress_ptr cinfo)
|
||||
{
|
||||
my_master_ptr master = (my_master_ptr)cinfo->master;
|
||||
|
||||
switch (master->pass_type) {
|
||||
case main_pass:
|
||||
/* Initial pass: will collect input data, and do either Huffman
|
||||
* optimization or data output for the first scan.
|
||||
*/
|
||||
select_scan_parameters(cinfo);
|
||||
per_scan_setup(cinfo);
|
||||
if (!cinfo->raw_data_in) {
|
||||
(*cinfo->cconvert->start_pass) (cinfo);
|
||||
(*cinfo->downsample->start_pass) (cinfo);
|
||||
(*cinfo->prep->start_pass) (cinfo, JBUF_PASS_THRU);
|
||||
}
|
||||
(*cinfo->fdct->start_pass) (cinfo);
|
||||
(*cinfo->entropy->start_pass) (cinfo, cinfo->optimize_coding);
|
||||
(*cinfo->coef->start_pass) (cinfo,
|
||||
(master->total_passes > 1 ?
|
||||
JBUF_SAVE_AND_PASS : JBUF_PASS_THRU));
|
||||
(*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU);
|
||||
if (cinfo->optimize_coding) {
|
||||
/* No immediate data output; postpone writing frame/scan headers */
|
||||
master->pub.call_pass_startup = FALSE;
|
||||
} else {
|
||||
/* Will write frame/scan headers at first jpeg_write_scanlines call */
|
||||
master->pub.call_pass_startup = TRUE;
|
||||
}
|
||||
break;
|
||||
#ifdef ENTROPY_OPT_SUPPORTED
|
||||
case huff_opt_pass:
|
||||
/* Do Huffman optimization for a scan after the first one. */
|
||||
select_scan_parameters(cinfo);
|
||||
per_scan_setup(cinfo);
|
||||
if (cinfo->Ss != 0 || cinfo->Ah == 0 || cinfo->arith_code ||
|
||||
cinfo->master->lossless) {
|
||||
(*cinfo->entropy->start_pass) (cinfo, TRUE);
|
||||
(*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST);
|
||||
master->pub.call_pass_startup = FALSE;
|
||||
break;
|
||||
}
|
||||
/* Special case: Huffman DC refinement scans need no Huffman table
|
||||
* and therefore we can skip the optimization pass for them.
|
||||
*/
|
||||
master->pass_type = output_pass;
|
||||
master->pass_number++;
|
||||
#endif
|
||||
FALLTHROUGH /*FALLTHROUGH*/
|
||||
case output_pass:
|
||||
/* Do a data-output pass. */
|
||||
/* We need not repeat per-scan setup if prior optimization pass did it. */
|
||||
if (!cinfo->optimize_coding) {
|
||||
select_scan_parameters(cinfo);
|
||||
per_scan_setup(cinfo);
|
||||
}
|
||||
(*cinfo->entropy->start_pass) (cinfo, FALSE);
|
||||
(*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST);
|
||||
/* We emit frame/scan headers now */
|
||||
if (master->scan_number == 0)
|
||||
(*cinfo->marker->write_frame_header) (cinfo);
|
||||
(*cinfo->marker->write_scan_header) (cinfo);
|
||||
master->pub.call_pass_startup = FALSE;
|
||||
break;
|
||||
default:
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
}
|
||||
|
||||
master->pub.is_last_pass = (master->pass_number == master->total_passes - 1);
|
||||
|
||||
/* Set up progress monitor's pass info if present */
|
||||
if (cinfo->progress != NULL) {
|
||||
cinfo->progress->completed_passes = master->pass_number;
|
||||
cinfo->progress->total_passes = master->total_passes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Special start-of-pass hook.
|
||||
* This is called by jpeg_write_scanlines if call_pass_startup is TRUE.
|
||||
* In single-pass processing, we need this hook because we don't want to
|
||||
* write frame/scan headers during jpeg_start_compress; we want to let the
|
||||
* application write COM markers etc. between jpeg_start_compress and the
|
||||
* jpeg_write_scanlines loop.
|
||||
* In multi-pass processing, this routine is not used.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
pass_startup(j_compress_ptr cinfo)
|
||||
{
|
||||
cinfo->master->call_pass_startup = FALSE; /* reset flag so call only once */
|
||||
|
||||
(*cinfo->marker->write_frame_header) (cinfo);
|
||||
(*cinfo->marker->write_scan_header) (cinfo);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Finish up at end of pass.
|
||||
*/
|
||||
|
||||
METHODDEF(void)
|
||||
finish_pass_master(j_compress_ptr cinfo)
|
||||
{
|
||||
my_master_ptr master = (my_master_ptr)cinfo->master;
|
||||
|
||||
/* The entropy coder always needs an end-of-pass call,
|
||||
* either to analyze statistics or to flush its output buffer.
|
||||
*/
|
||||
(*cinfo->entropy->finish_pass) (cinfo);
|
||||
|
||||
/* Update state for next pass */
|
||||
switch (master->pass_type) {
|
||||
case main_pass:
|
||||
/* next pass is either output of scan 0 (after optimization)
|
||||
* or output of scan 1 (if no optimization).
|
||||
*/
|
||||
master->pass_type = output_pass;
|
||||
if (!cinfo->optimize_coding)
|
||||
master->scan_number++;
|
||||
break;
|
||||
case huff_opt_pass:
|
||||
/* next pass is always output of current scan */
|
||||
master->pass_type = output_pass;
|
||||
break;
|
||||
case output_pass:
|
||||
/* next pass is either optimization or output of next scan */
|
||||
if (cinfo->optimize_coding)
|
||||
master->pass_type = huff_opt_pass;
|
||||
master->scan_number++;
|
||||
break;
|
||||
}
|
||||
|
||||
master->pass_number++;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize master compression control.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jinit_c_master_control(j_compress_ptr cinfo, boolean transcode_only)
|
||||
{
|
||||
my_master_ptr master = (my_master_ptr)cinfo->master;
|
||||
boolean empty_huff_tables = TRUE;
|
||||
int i;
|
||||
|
||||
master->pub.prepare_for_pass = prepare_for_pass;
|
||||
master->pub.pass_startup = pass_startup;
|
||||
master->pub.finish_pass = finish_pass_master;
|
||||
master->pub.is_last_pass = FALSE;
|
||||
|
||||
if (cinfo->scan_info != NULL) {
|
||||
#ifdef NEED_SCAN_SCRIPT
|
||||
validate_script(cinfo);
|
||||
#else
|
||||
ERREXIT(cinfo, JERR_NOT_COMPILED);
|
||||
#endif
|
||||
} else {
|
||||
cinfo->progressive_mode = FALSE;
|
||||
cinfo->num_scans = 1;
|
||||
}
|
||||
|
||||
/* Disable smoothing and subsampling in lossless mode, since those are lossy
|
||||
* algorithms. Set the JPEG colorspace to the input colorspace. Disable raw
|
||||
* (downsampled) data input, because it isn't particularly useful without
|
||||
* subsampling and has not been tested in lossless mode.
|
||||
*/
|
||||
if (cinfo->master->lossless) {
|
||||
int ci;
|
||||
jpeg_component_info *compptr;
|
||||
|
||||
cinfo->raw_data_in = FALSE;
|
||||
cinfo->smoothing_factor = 0;
|
||||
jpeg_default_colorspace(cinfo);
|
||||
for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
|
||||
ci++, compptr++)
|
||||
compptr->h_samp_factor = compptr->v_samp_factor = 1;
|
||||
}
|
||||
|
||||
/* Validate parameters, determine derived values */
|
||||
initial_setup(cinfo, transcode_only);
|
||||
|
||||
if (cinfo->arith_code)
|
||||
cinfo->optimize_coding = FALSE;
|
||||
else {
|
||||
if (cinfo->master->lossless || /* TEMPORARY HACK ??? */
|
||||
cinfo->progressive_mode)
|
||||
cinfo->optimize_coding = TRUE; /* assume default tables no good for
|
||||
progressive mode or lossless mode */
|
||||
for (i = 0; i < NUM_HUFF_TBLS; i++) {
|
||||
if (cinfo->dc_huff_tbl_ptrs[i] != NULL ||
|
||||
cinfo->ac_huff_tbl_ptrs[i] != NULL) {
|
||||
empty_huff_tables = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cinfo->data_precision == 12 && !cinfo->optimize_coding &&
|
||||
(empty_huff_tables || using_std_huff_tables(cinfo)))
|
||||
cinfo->optimize_coding = TRUE; /* assume default tables no good for
|
||||
12-bit data precision */
|
||||
}
|
||||
|
||||
/* Initialize my private state */
|
||||
if (transcode_only) {
|
||||
/* no main pass in transcoding */
|
||||
if (cinfo->optimize_coding)
|
||||
master->pass_type = huff_opt_pass;
|
||||
else
|
||||
master->pass_type = output_pass;
|
||||
} else {
|
||||
/* for normal compression, first pass is always this type: */
|
||||
master->pass_type = main_pass;
|
||||
}
|
||||
master->scan_number = 0;
|
||||
master->pass_number = 0;
|
||||
if (cinfo->optimize_coding)
|
||||
master->total_passes = cinfo->num_scans * 2;
|
||||
else
|
||||
master->total_passes = cinfo->num_scans;
|
||||
|
||||
master->jpeg_version = PACKAGE_NAME " version " VERSION " (build " BUILD ")";
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* jcmaster.h
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1991-1995, Thomas G. Lane.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2016, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains master control structure for the JPEG compressor.
|
||||
*/
|
||||
|
||||
/* Private state */
|
||||
|
||||
typedef enum {
|
||||
main_pass, /* input data, also do first output step */
|
||||
huff_opt_pass, /* Huffman code optimization pass */
|
||||
output_pass /* data output pass */
|
||||
} c_pass_type;
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_comp_master pub; /* public fields */
|
||||
|
||||
c_pass_type pass_type; /* the type of the current pass */
|
||||
|
||||
int pass_number; /* # of passes completed */
|
||||
int total_passes; /* total # of passes needed */
|
||||
|
||||
int scan_number; /* current index in scan_info[] */
|
||||
|
||||
/*
|
||||
* This is here so we can add libjpeg-turbo version/build information to the
|
||||
* global string table without introducing a new global symbol. Adding this
|
||||
* information to the global string table allows one to examine a binary
|
||||
* object and determine which version of libjpeg-turbo it was built from or
|
||||
* linked against.
|
||||
*/
|
||||
const char *jpeg_version;
|
||||
|
||||
} my_comp_master;
|
||||
|
||||
typedef my_comp_master *my_master_ptr;
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* jcomapi.c
|
||||
*
|
||||
* This file was part of the Independent JPEG Group's software:
|
||||
* Copyright (C) 1994-1997, Thomas G. Lane.
|
||||
* libjpeg-turbo Modifications:
|
||||
* Copyright (C) 2024, D. R. Commander.
|
||||
* For conditions of distribution and use, see the accompanying README.ijg
|
||||
* file.
|
||||
*
|
||||
* This file contains application interface routines that are used for both
|
||||
* compression and decompression.
|
||||
*/
|
||||
|
||||
#define JPEG_INTERNALS
|
||||
#include "jinclude.h"
|
||||
#include "jpeglib.h"
|
||||
|
||||
|
||||
/*
|
||||
* Abort processing of a JPEG compression or decompression operation,
|
||||
* but don't destroy the object itself.
|
||||
*
|
||||
* For this, we merely clean up all the nonpermanent memory pools.
|
||||
* Note that temp files (virtual arrays) are not allowed to belong to
|
||||
* the permanent pool, so we will be able to close all temp files here.
|
||||
* Closing a data source or destination, if necessary, is the application's
|
||||
* responsibility.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_abort(j_common_ptr cinfo)
|
||||
{
|
||||
int pool;
|
||||
|
||||
/* Do nothing if called on a not-initialized or destroyed JPEG object. */
|
||||
if (cinfo->mem == NULL)
|
||||
return;
|
||||
|
||||
/* Releasing pools in reverse order might help avoid fragmentation
|
||||
* with some (brain-damaged) malloc libraries.
|
||||
*/
|
||||
for (pool = JPOOL_NUMPOOLS - 1; pool > JPOOL_PERMANENT; pool--) {
|
||||
(*cinfo->mem->free_pool) (cinfo, pool);
|
||||
}
|
||||
|
||||
/* Reset overall state for possible reuse of object */
|
||||
if (cinfo->is_decompressor) {
|
||||
cinfo->global_state = DSTATE_START;
|
||||
/* Try to keep application from accessing now-deleted marker list.
|
||||
* A bit kludgy to do it here, but this is the most central place.
|
||||
*/
|
||||
((j_decompress_ptr)cinfo)->marker_list = NULL;
|
||||
((j_decompress_ptr)cinfo)->master->marker_list_end = NULL;
|
||||
} else {
|
||||
cinfo->global_state = CSTATE_START;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destruction of a JPEG object.
|
||||
*
|
||||
* Everything gets deallocated except the master jpeg_compress_struct itself
|
||||
* and the error manager struct. Both of these are supplied by the application
|
||||
* and must be freed, if necessary, by the application. (Often they are on
|
||||
* the stack and so don't need to be freed anyway.)
|
||||
* Closing a data source or destination, if necessary, is the application's
|
||||
* responsibility.
|
||||
*/
|
||||
|
||||
GLOBAL(void)
|
||||
jpeg_destroy(j_common_ptr cinfo)
|
||||
{
|
||||
/* We need only tell the memory manager to release everything. */
|
||||
/* NB: mem pointer is NULL if memory mgr failed to initialize. */
|
||||
if (cinfo->mem != NULL)
|
||||
(*cinfo->mem->self_destruct) (cinfo);
|
||||
cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */
|
||||
cinfo->global_state = 0; /* mark it destroyed */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convenience routines for allocating quantization and Huffman tables.
|
||||
* (Would jutils.c be a more reasonable place to put these?)
|
||||
*/
|
||||
|
||||
GLOBAL(JQUANT_TBL *)
|
||||
jpeg_alloc_quant_table(j_common_ptr cinfo)
|
||||
{
|
||||
JQUANT_TBL *tbl;
|
||||
|
||||
tbl = (JQUANT_TBL *)
|
||||
(*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, sizeof(JQUANT_TBL));
|
||||
tbl->sent_table = FALSE; /* make sure this is false in any new table */
|
||||
return tbl;
|
||||
}
|
||||
|
||||
|
||||
GLOBAL(JHUFF_TBL *)
|
||||
jpeg_alloc_huff_table(j_common_ptr cinfo)
|
||||
{
|
||||
JHUFF_TBL *tbl;
|
||||
|
||||
tbl = (JHUFF_TBL *)
|
||||
(*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, sizeof(JHUFF_TBL));
|
||||
tbl->sent_table = FALSE; /* make sure this is false in any new table */
|
||||
return tbl;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user