Merge pull request #759 from aYukiSekiguchi/fix_hIST_chunk_position

Fix hIST chunk position to match W3C specification
diff --git a/.appveyor.yml b/.appveyor.yml
index 13c0414..b89ff62 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -2,8 +2,8 @@
 
 branches:
   except:
-    - /libpng[0-1][0-8]/
-    - /v[0-1][.][0-8][.][0-9]+/
+    - /master/
+    - /v[0-9]+[.][0-9]+[.][0-9]+/
 
 image:
   - Visual Studio 2022
@@ -44,11 +44,13 @@
       ARCH: x86_64
 
 install:
-  - 'if "%TOOLCHAIN%"=="vstudio" C:\tools\vcpkg\vcpkg.exe install zlib:%ARCH%-windows'
   - 'if "%TOOLCHAIN%"=="vstudio" C:\tools\vcpkg\vcpkg.exe integrate install'
-  - 'if "%TOOLCHAIN%"=="llvm" C:\tools\vcpkg\vcpkg.exe install zlib:%ARCH%-windows'
+  - 'if "%TOOLCHAIN%"=="vstudio" C:\tools\vcpkg\vcpkg.exe install zlib:%ARCH%-windows'
   - 'if "%TOOLCHAIN%"=="llvm" C:\tools\vcpkg\vcpkg.exe integrate install'
+  - 'if "%TOOLCHAIN%"=="llvm" C:\tools\vcpkg\vcpkg.exe install zlib:%ARCH%-windows'
+  - 'if "%TOOLCHAIN%"=="msys2" C:\msys64\usr\bin\pacman.exe -S --noconfirm mingw-w64-%ARCH%-zlib'
   - 'if "%TOOLCHAIN%"=="msys2" if "%AUTOMATION%"=="cmake" C:\msys64\usr\bin\pacman.exe -S --noconfirm mingw-w64-%ARCH%-cmake mingw-w64-%ARCH%-ninja'
+  - 'if "%TOOLCHAIN%"=="msys2" if "%AUTOMATION%"=="configure" C:\msys64\usr\bin\pacman.exe -S --noconfirm autoconf automake libtool'
 
 before_build:
   - 'if "%TOOLCHAIN%"=="vstudio" set CI_CMAKE_GENERATOR=Visual Studio 17 2022'
diff --git a/.editorconfig-checker.json b/.editorconfig-checker.json
index ef08e08..70eb654 100644
--- a/.editorconfig-checker.json
+++ b/.editorconfig-checker.json
@@ -1,5 +1,6 @@
 {
   "Disable": {
+    "Charset": true,
     "IndentSize": true
   },
   "Exclude": [
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 65a63b4..12318ea 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,3 +1,6 @@
+# Copyright (C) 2024 Cosmin Truta
+# SPDX-License-Identifier: MIT-0
+
 name: Linting libpng
 
 on:
diff --git a/.github/workflows/verify-linux.yml b/.github/workflows/verify-linux.yml
index a6f5a6b..da048f3 100644
--- a/.github/workflows/verify-linux.yml
+++ b/.github/workflows/verify-linux.yml
@@ -1,3 +1,6 @@
+# Copyright (C) 2025 Cosmin Truta
+# SPDX-License-Identifier: MIT-0
+
 name: Verifying libpng on Linux
 
 on:
diff --git a/.github/workflows/verify-macos.yml b/.github/workflows/verify-macos.yml
index 51850d4..d61174f 100644
--- a/.github/workflows/verify-macos.yml
+++ b/.github/workflows/verify-macos.yml
@@ -1,3 +1,6 @@
+# Copyright (C) 2025 Cosmin Truta
+# SPDX-License-Identifier: MIT-0
+
 name: Verifying libpng on macOS
 
 on:
diff --git a/.github/workflows/verify-windows.yml b/.github/workflows/verify-windows.yml
index b559c76..5f9bd33 100644
--- a/.github/workflows/verify-windows.yml
+++ b/.github/workflows/verify-windows.yml
@@ -1,3 +1,6 @@
+# Copyright (C) 2025 Cosmin Truta
+# SPDX-License-Identifier: MIT-0
+
 name: Verifying libpng on Windows
 
 on:
@@ -12,26 +15,37 @@
 
 jobs:
   verify-windows:
-    runs-on: windows-latest
+    strategy:
+      matrix:
+        include:
+          - os: windows-2025
+            vcpkg_triplet: x64-windows
+            cmake_generator: Visual Studio 18 2026
+            cmake_generator_platform: x64
+          - os: windows-11-arm
+            vcpkg_triplet: arm64-windows
+            cmake_generator: Visual Studio 17 2022
+            cmake_generator_platform: ARM64
+    runs-on: ${{ matrix.os }}
     timeout-minutes: 10
     steps:
       - name: Set up vcpkg cache
         uses: actions/cache@v4
         with:
           path: C:/vcpkg/installed
-          key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json', '**/vcpkg.json') }}
-          restore-keys: ${{ runner.os }}-vcpkg-
+          key: ${{ runner.os }}-vcpkg-${{ matrix.vcpkg_triplet }}-${{ hashFiles('vcpkg.json', '**/vcpkg.json') }}
+          restore-keys: ${{ runner.os }}-vcpkg-${{ matrix.vcpkg_triplet }}-
       - name: Install dependencies
         run: |
-          vcpkg install zlib:x64-windows
+          vcpkg install zlib:${{ matrix.vcpkg_triplet }}
           vcpkg integrate install
       - name: Check out the code
         uses: actions/checkout@v4
       - name: Run the CMake verification script
         run: bash ./ci/ci_verify_cmake.sh
         env:
-          CI_CMAKE_GENERATOR: Visual Studio 17 2022
-          CI_CMAKE_GENERATOR_PLATFORM: x64
+          CI_CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
+          CI_CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
           CI_CMAKE_TOOLCHAIN_FILE: C:/vcpkg/scripts/buildsystems/vcpkg.cmake
           CI_CMAKE_BUILD_FLAGS: -j2
           CI_CTEST_FLAGS: -j2
diff --git a/.gitignore b/.gitignore
index 19d179e..3d08cb3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -105,7 +105,8 @@
 CMakeLists.txt.user*
 CMakeUserPresets.json
 
-# Local configuration files and prompts for coding agents
+# Local files created or used by coding agents
+*.local
 *.local.*
 
 # Build, test and CI output directories
@@ -161,6 +162,7 @@
 /png-fix-itxt
 /pngcp
 /pngfix
+/pnggetset
 /pngimage
 /pngstest
 /pngtest
diff --git a/.markdownlint.yml b/.markdownlint.yml
new file mode 100644
index 0000000..5072a31
--- /dev/null
+++ b/.markdownlint.yml
@@ -0,0 +1,25 @@
+# Markdownlint configuration
+# See https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md
+
+# MD004
+ul-style:
+  style: sublist
+
+# MD007
+ul-indent:
+  start_indented: true
+  start_indent: 1
+  indent: 3
+
+# MD012
+no-multiple-blanks:
+  maximum: 2
+
+# MD024
+no-duplicate-heading: false
+
+# MD025
+single-title: false
+
+# MD028
+no-blanks-blockquote: false
diff --git a/AUTHORS.md b/AUTHORS.md
index 4c4d09d..be1c9b6 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -5,7 +5,9 @@
 Authors, for copyright and licensing purposes.
 
  * Adam Richter
+ * Alexander Smorkalov
  * Andreas Dilger
+ * Anthony Hurtado
  * Chris Blume
  * Cosmin Truta
  * Daisuke Nishikawa
@@ -15,8 +17,10 @@
  * Glenn Randers-Pehrson
  * Greg Roelofs
  * Guy Eric Schalnat
+ * Halil Oktay
  * James Yu
  * John Bowler
+ * Joshua Inscoe
  * Kevin Bracey
  * Lucas Chollet
  * Maarten Bent
@@ -26,19 +30,23 @@
  * Mans Rullgard
  * Matt Sarett
  * Mike Klein
+ * Mohammad Seet
  * Pascal Massimino
  * Paul Schmidt
+ * Petr Simecek
  * Philippe Antoine
  * Qiang Zhou
  * Sam Bushell
  * Samuel Williams
  * Simon-Pierre Cadieux
+ * Taegu Ha (하태구)
  * Tim Wegner
  * Tobias Stoeckmann
  * Tom Lane
  * Tom Tanner
  * Vadim Barkov
  * Willem van Schaik
+ * Yuelin Wang (王跃林)
  * Zhijie Liang
  * Apple Inc.
     - Zixu Wang (王子旭)
@@ -58,6 +66,8 @@
     - ZhangLixia (张利霞)
  * Samsung Group
     - Filip Wasil
+ * SpacemiT Hangzhou Technology, Co.
+    - Liang Junzhao (梁俊钊)
 
 The build projects, the build scripts, the test scripts, and other
 files in the "projects", "scripts" and "tests" directories, have
diff --git a/CHANGES b/CHANGES
index a653d4b..60c262f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -5988,7 +5988,7 @@
 
 Version 1.6.32rc02 [August 22, 2017]
   Added contrib/oss-fuzz directory which contains files used by the oss-fuzz
-    project (https://github.com/google/oss-fuzz/tree/master/projects/libpng).
+    project <https://github.com/google/oss-fuzz/tree/master/projects/libpng>.
 
 Version 1.6.32 [August 24, 2017]
   No changes.
@@ -6278,9 +6278,116 @@
     (Contributed by John Bowler)
   Fixed the CMake file for cross-platform builds that require `libm`.
 
+Version 1.6.51 [November 21, 2025]
+  Fixed CVE-2025-64505 (moderate severity):
+    Heap buffer overflow in `png_do_quantize` via malformed palette index.
+    (Reported by Samsung; analyzed by Fabio Gritti.)
+  Fixed CVE-2025-64506 (moderate severity):
+    Heap buffer over-read in `png_write_image_8bit` with 8-bit input and
+    `convert_to_8bit` enabled.
+    (Reported by Samsung and <weijinjinnihao@users.noreply.github.com>;
+    analyzed by Fabio Gritti.)
+  Fixed CVE-2025-64720 (high severity):
+    Buffer overflow in `png_image_read_composite` via incorrect palette
+    premultiplication.
+    (Reported by Samsung; analyzed by John Bowler.)
+  Fixed CVE-2025-65018 (high severity):
+    Heap buffer overflow in `png_combine_row` triggered via
+    `png_image_finish_read`.
+    (Reported by <yosiimich@users.noreply.github.com>.)
+  Fixed a memory leak in `png_set_quantize`.
+    (Reported by Samsung; analyzed by Fabio Gritti.)
+  Removed the experimental and incomplete ERROR_NUMBERS code.
+    (Contributed by Tobias Stoeckmann.)
+  Improved the RISC-V vector extension support; required RVV 1.0 or newer.
+    (Contributed by Filip Wasil.)
+  Added GitHub Actions workflows for automated testing.
+  Performed various refactorings and cleanups.
+
+Version 1.6.52 [December 3, 2025]
+  Fixed CVE-2025-66293 (high severity):
+    Out-of-bounds read in `png_image_read_composite`.
+    (Reported by flyfish101 <flyfish101@users.noreply.github.com>.)
+  Fixed the Paeth filter handling in the RISC-V RVV implementation.
+    (Reported by Filip Wasil; fixed by Liang Junzhao.)
+  Improved the performance of the RISC-V RVV implementation.
+    (Contributed by Liang Junzhao.)
+  Added allocation failure fuzzing to oss-fuzz.
+    (Contributed by Philippe Antoine.)
+
+Version 1.6.53 [December 5, 2025]
+  Fixed a build failure on RISC-V RVV caused by a misspelled intrinsic.
+    (Contributed by Alexander Smorkalov.)
+  Fixed a build failure with CMake 4.1 or newer, on Windows, when using
+    Visual C++ without MASM installed.
+
+Version 1.6.54 [January 12, 2026]
+  Fixed CVE-2026-22695 (medium severity):
+    Heap buffer over-read in `png_image_read_direct_scaled`.
+    (Reported and fixed by Petr Simecek.)
+  Fixed CVE-2026-22801 (medium severity):
+    Integer truncation causing heap buffer over-read in `png_image_write_*`.
+  Implemented various improvements in oss-fuzz.
+    (Contributed by Philippe Antoine.)
+
+Version 1.6.55 [February 9, 2026]
+  Fixed CVE-2026-25646 (high severity):
+    Heap buffer overflow in `png_set_quantize`.
+    (Reported and fixed by Joshua Inscoe.)
+  Resolved an oss-fuzz build issue involving nalloc.
+    (Contributed by Philippe Antoine.)
+
+Version 1.6.56 [March 25, 2026]
+  Fixed CVE-2026-33416 (high severity):
+    Use-after-free via pointer aliasing in `png_set_tRNS` and `png_set_PLTE`.
+    (Reported by Halil Oktay and Ryo Shimada;
+    fixed by Halil Oktay and Cosmin Truta.)
+  Fixed CVE-2026-33636 (high severity):
+    Out-of-bounds read/write in the palette expansion on ARM Neon.
+    (Reported by Taegu Ha; fixed by Taegu Ha and Cosmin Truta.)
+  Fixed uninitialized reads beyond `num_trans` in `trans_alpha` buffers.
+    (Contributed by Halil Oktay.)
+  Fixed stale `info_ptr->palette` after in-place gamma and background
+    transforms.
+  Fixed wrong channel indices in `png_image_read_and_map` RGB_ALPHA path.
+    (Contributed by Yuelin Wang.)
+  Fixed wrong background color in colormap read.
+    (Contributed by Yuelin Wang.)
+  Fixed dead loop in sPLT write.
+    (Contributed by Yuelin Wang.)
+  Added missing null pointer checks in four public API functions.
+    (Contributed by Yuelin Wang.)
+  Validated shift bit depths in `png_set_shift` to prevent infinite loop.
+    (Contributed by Yuelin Wang.)
+  Avoided undefined behavior in library and tests.
+  Deprecated the hardly-ever-tested POINTER_INDEXING config option.
+  Added negative-stride test coverage for the simplified API.
+  Fixed memory leaks and API misuse in oss-fuzz.
+    (Contributed by Owen Sanzas.)
+  Implemented various fixes and improvements in oss-fuzz.
+    (Contributed by Bob Friesenhahn and Philippe Antoine.)
+  Performed various refactorings and cleanups.
+
+Version 1.6.57 [April 8, 2026]
+  Fixed CVE-2026-34757 (medium severity):
+    Use-after-free in `png_set_PLTE`, `png_set_tRNS` and `png_set_hIST`
+    leading to corrupted chunk data and potential heap information disclosure.
+    Also hardened the append-style setters (`png_set_text`, `png_set_sPLT`,
+    `png_set_unknown_chunks`) against a theoretical variant of the same
+    aliasing pattern.
+    (Reported by Iv4n <Iv4n550@users.noreply.github.com>.)
+  Fixed integer overflow in rowbytes computation in read transforms.
+    (Contributed by Mohammad Seet.)
+
+Version 1.6.58 [April 15, 2026]
+  Fixed a regression introduced in version 1.6.56 that caused `png_get_PLTE`
+    to return stale palette data after applying gamma and background transforms
+    in-place.
+    (Reported by ralfjunker <ralfjunker@users.noreply.github.com>.)
+
 Version 1.8.0 [TODO]
 
 Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
 Subscription is required; visit
-https://lists.sourceforge.net/lists/listinfo/png-mng-implement
+<https://lists.sourceforge.net/lists/listinfo/png-mng-implement>
 to subscribe.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 18e2026..162d63a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 # CMakeLists.txt - CMake lists for libpng
 #
-# Copyright (c) 2018-2025 Cosmin Truta
+# Copyright (c) 2018-2026 Cosmin Truta
 # Copyright (c) 2007-2018 Glenn Randers-Pehrson
 # Originally written by Christian Ehrlicher, 2007
 #
@@ -14,7 +14,7 @@
 #
 # SPDX-License-Identifier: libpng-2.0
 
-cmake_minimum_required(VERSION 3.14...4.0)
+cmake_minimum_required(VERSION 3.14...4.4)
 
 set(PNGLIB_MAJOR 1)
 set(PNGLIB_MINOR 8)
@@ -27,7 +27,7 @@
 
 project(libpng
         VERSION ${PNGLIB_VERSION}
-        LANGUAGES C ASM)
+        LANGUAGES C)
 
 include(CheckCSourceCompiles)
 include(CheckLibraryExists)
@@ -87,7 +87,7 @@
 option(PNG_TESTS "Build the libpng tests" ON)
 
 # Same as above, but for the third-party tools.
-# Although these tools are targetted at development environments only,
+# Although these tools are targeted at development environments only,
 # the users are allowed to override the option to build by default.
 if(ANDROID OR IOS)
   option(PNG_TOOLS "Build the libpng tools" OFF)
@@ -164,34 +164,37 @@
 VERS_1 { global: sym1; local: *; };
 VERS_2 { global: sym2; main; } VERS_1;
 ")
-  set(_SAVED_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
+  # CMAKE_REQUIRED_FLAGS is a space-separated string, not a CMake list.
+  # Use string(APPEND) instead of list(APPEND) to prevent misparsing by
+  # try_compile under CMake 4.4 or newer.
+  set(_SAVED_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
   if(NOT CMAKE_HOST_SOLARIS)
     # Avoid using CMAKE_SHARED_LIBRARY_C_FLAGS in version script checks on
     # Solaris, because of an incompatibility with the Solaris link editor.
-    list(APPEND CMAKE_REQUIRED_FLAGS ${CMAKE_SHARED_LIBRARY_C_FLAGS})
+    string(APPEND CMAKE_REQUIRED_FLAGS " ${CMAKE_SHARED_LIBRARY_C_FLAGS}")
   endif()
-  list(APPEND CMAKE_REQUIRED_FLAGS
-              "-Wl,--version-script='${CMAKE_CURRENT_BINARY_DIR}/conftest.map'")
+  string(APPEND CMAKE_REQUIRED_FLAGS
+                " -Wl,--version-script='${CMAKE_CURRENT_BINARY_DIR}/conftest.map'")
   check_c_source_compiles("
 void sym1(void) {}
 void sym2(void) {}
 int main(void) { return 0; }
 " HAVE_LD_VERSION_SCRIPT)
   if(NOT HAVE_LD_VERSION_SCRIPT)
-    set(CMAKE_REQUIRED_FLAGS ${_SAVED_CMAKE_REQUIRED_FLAGS})
+    set(CMAKE_REQUIRED_FLAGS "${_SAVED_CMAKE_REQUIRED_FLAGS}")
     if(NOT CMAKE_HOST_SOLARIS)
       # Again, avoid using CMAKE_SHARED_LIBRARY_C_FLAGS in version script
       # checks on Solaris.
-      list(APPEND CMAKE_REQUIRED_FLAGS ${CMAKE_SHARED_LIBRARY_C_FLAGS})
+      string(APPEND CMAKE_REQUIRED_FLAGS " ${CMAKE_SHARED_LIBRARY_C_FLAGS}")
     endif()
-    list(APPEND CMAKE_REQUIRED_FLAGS "-Wl,-M -Wl,${CMAKE_CURRENT_BINARY_DIR}/conftest.map")
+    string(APPEND CMAKE_REQUIRED_FLAGS " -Wl,-M -Wl,${CMAKE_CURRENT_BINARY_DIR}/conftest.map")
     check_c_source_compiles("
 void sym1(void) {}
 void sym2(void) {}
 int main(void) { return 0; }
 " HAVE_SOLARIS_LD_VERSION_SCRIPT)
   endif()
-  set(CMAKE_REQUIRED_FLAGS ${_SAVED_CMAKE_REQUIRED_FLAGS})
+  set(CMAKE_REQUIRED_FLAGS "${_SAVED_CMAKE_REQUIRED_FLAGS}")
   file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/conftest.map")
 endif()
 
@@ -387,6 +390,9 @@
 set(pngstest_sources
     contrib/libtests/pngstest.c
 )
+set(pnggetset_sources
+    contrib/libtests/pnggetset.c
+)
 set(pngunknown_sources
     contrib/libtests/pngunknown.c
 )
@@ -535,6 +541,8 @@
 
   set(PNGTEST_PNG "${CMAKE_CURRENT_SOURCE_DIR}/pngtest.png")
 
+  # pngtest tests:
+  # Basic read/write roundtrip using the sequential API.
   add_executable(pngtest ${pngtest_sources})
   target_link_libraries(pngtest
                         PRIVATE png_shared)
@@ -547,6 +555,18 @@
                COMMAND pngtest
                FILES "${TEST_PNG3_PNGS}")
 
+  # pnggetset test:
+  # Getter-to-setter roundtrips for various chunk types.
+  add_executable(pnggetset ${pnggetset_sources})
+  target_link_libraries(pnggetset
+                        PRIVATE png_shared)
+
+  png_add_test(NAME pnggetset
+               COMMAND pnggetset)
+
+  # pngvalid tests:
+  # Internal validation of standard and progressive reading,
+  # transforms, and gamma handling.
   add_executable(pngvalid ${pngvalid_sources})
   target_link_libraries(pngvalid
                         PRIVATE png_shared)
@@ -594,6 +614,9 @@
                COMMAND pngvalid
                OPTIONS --transform)
 
+  # pngstest tests:
+  # Format conversions through the simplified API,
+  # by gamma type and alpha type.
   add_executable(pngstest ${pngstest_sources})
   target_link_libraries(pngstest
                         PRIVATE png_shared)
@@ -650,6 +673,48 @@
     endforeach()
   endforeach()
 
+  # Large-stride test:
+  # Use stride_extra > 32767 to trigger row_bytes > 65535 for linear images.
+  png_add_test(NAME pngstest-large-stride
+               COMMAND pngstest
+               OPTIONS --stride-extra 33000 --tmpfile "large-stride-" --log
+               FILES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-alpha-16-linear.png")
+
+  # Negative-stride test:
+  # Bottom-up layout through all read/write paths.
+  png_add_test(NAME pngstest-negative-stride
+               COMMAND pngstest
+               OPTIONS --negative-stride --tmpfile "negative-stride-" --log
+               FILES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/gray-1.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/gray-16-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-8-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-16-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/palette-8-tRNS.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/gray-alpha-8-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/gray-alpha-16-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-alpha-8-1.8.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-alpha-8-sRGB.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-alpha-16-linear.png")
+
+  # Negative-stride-extra test:
+  # Bottom-up layout with non-aligned padding.
+  png_add_test(NAME pngstest-negative-stride-extra
+               COMMAND pngstest
+               OPTIONS --negative-stride --stride-extra 7
+                       --tmpfile "negative-stride-extra-" --log
+               FILES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/gray-1.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/gray-16-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-8-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-16-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/palette-8-tRNS.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/gray-alpha-8-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/gray-alpha-16-linear.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-alpha-8-1.8.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-alpha-8-sRGB.png"
+                     "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-alpha-16-linear.png")
+
+  # pngunknown tests:
+  # Unknown chunk handling under various read policies.
   add_executable(pngunknown ${pngunknown_sources})
   target_link_libraries(pngunknown
                         PRIVATE png_shared)
@@ -684,6 +749,8 @@
                OPTIONS --strict vpAg=if-safe
                FILES "${PNGTEST_PNG}")
 
+  # pngimage tests:
+  # Image read validation against the pngsuite corpus.
   add_executable(pngimage ${pngimage_sources})
   target_link_libraries(pngimage
                         PRIVATE png_shared)
@@ -723,7 +790,7 @@
     message(FATAL_ERROR "create_symlink: Missing arguments: FILE or TARGET")
   endif()
   if(_SYM_FILE AND _SYM_TARGET)
-    message(FATAL_ERROR "create_symlink: Mutually-exlusive arguments:"
+    message(FATAL_ERROR "create_symlink: Mutually-exclusive arguments:"
                         "FILE (${_SYM_FILE}) and TARGET (${_SYM_TARGET})")
   endif()
 
@@ -813,10 +880,17 @@
           FRAMEWORK DESTINATION "${CMAKE_INSTALL_LIBDIR}")
 
   if(PNG_SHARED)
-    # Create a symlink for libpng.dll.a => libpng18.dll.a on Cygwin
+    # Create an unversioned symlink to the shared library's link-time file.
     if(NOT WIN32 OR CYGWIN OR MINGW)
-      create_symlink(libpng${CMAKE_SHARED_LIBRARY_SUFFIX} TARGET png_shared)
-      install(FILES "$<TARGET_LINKER_FILE_DIR:png_shared>/libpng${CMAKE_SHARED_LIBRARY_SUFFIX}"
+      if(CYGWIN OR MINGW)
+        # The symlink is for the import library (e.g., libpng.dll.a => libpng18.dll.a).
+        set(libpng_symlink_name "libpng${CMAKE_IMPORT_LIBRARY_SUFFIX}")
+      else()
+        # The symlink is for the shared object (e.g., libpng.so => libpng18.so).
+        set(libpng_symlink_name "libpng${CMAKE_SHARED_LIBRARY_SUFFIX}")
+      endif()
+      create_symlink(${libpng_symlink_name} TARGET png_shared)
+      install(FILES "$<TARGET_LINKER_FILE_DIR:png_shared>/${libpng_symlink_name}"
               DESTINATION "${CMAKE_INSTALL_LIBDIR}")
     endif()
   endif()
diff --git a/LICENSE.md b/LICENSE.md
index ea6df98..1b765ae 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -4,8 +4,8 @@
 PNG Reference Library License version 2
 ---------------------------------------
 
- * Copyright (c) 1995-2025 The PNG Reference Library Authors.
- * Copyright (c) 2018-2025 Cosmin Truta.
+ * Copyright (c) 1995-2026 The PNG Reference Library Authors.
+ * Copyright (c) 2018-2026 Cosmin Truta.
  * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson.
  * Copyright (c) 1996-1997 Andreas Dilger.
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
diff --git a/LICENSES/MIT-0.txt b/LICENSES/MIT-0.txt
new file mode 100644
index 0000000..30c6295
--- /dev/null
+++ b/LICENSES/MIT-0.txt
@@ -0,0 +1,16 @@
+MIT No Attribution
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/ci/LICENSE_MIT.txt b/LICENSES/MIT.txt
similarity index 100%
rename from ci/LICENSE_MIT.txt
rename to LICENSES/MIT.txt
diff --git a/Makefile.am b/Makefile.am
index fc31e7a..eb70781 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
 # Makefile.am, the source file for Makefile.in (and hence Makefile), is
 #
-# Copyright (c) 2018-2025 Cosmin Truta
+# Copyright (c) 2018-2026 Cosmin Truta
 # Copyright (c) 2004-2016 Glenn Randers-Pehrson
 #
 # This code is released under the libpng license.
@@ -13,7 +13,7 @@
 
 # test programs - run on make check, make distcheck
 if ENABLE_TESTS
-check_PROGRAMS= pngtest pngunknown pngstest pngvalid pngimage pngcp
+check_PROGRAMS= pngtest pnggetset pngunknown pngstest pngvalid pngimage pngcp
 if HAVE_CLOCK_GETTIME
 check_PROGRAMS += timepng
 endif
@@ -42,6 +42,9 @@
 pngtest_SOURCES = pngtest.c
 pngtest_LDADD = libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@.la
 
+pnggetset_SOURCES = contrib/libtests/pnggetset.c
+pnggetset_LDADD = libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@.la
+
 pngvalid_SOURCES = contrib/libtests/pngvalid.c
 pngvalid_LDADD = libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@.la
 
@@ -73,21 +76,41 @@
 if ENABLE_TESTS
 TESTS =\
    tests/pngtest-all\
-   tests/pngvalid-gamma-16-to-8 tests/pngvalid-gamma-alpha-mode\
-   tests/pngvalid-gamma-background tests/pngvalid-gamma-expand16-alpha-mode\
+   tests/pnggetset\
+   tests/pngvalid-gamma-16-to-8\
+   tests/pngvalid-gamma-alpha-mode\
+   tests/pngvalid-gamma-background\
+   tests/pngvalid-gamma-expand16-alpha-mode\
    tests/pngvalid-gamma-expand16-background\
-   tests/pngvalid-gamma-expand16-transform tests/pngvalid-gamma-sbit\
-   tests/pngvalid-gamma-threshold tests/pngvalid-gamma-transform\
+   tests/pngvalid-gamma-expand16-transform\
+   tests/pngvalid-gamma-sbit\
+   tests/pngvalid-gamma-threshold\
+   tests/pngvalid-gamma-transform\
    tests/pngvalid-progressive-size\
    tests/pngvalid-progressive-interlace-standard\
    tests/pngvalid-transform\
-   tests/pngvalid-progressive-standard tests/pngvalid-standard\
-   tests/pngstest-1.8 tests/pngstest-1.8-alpha tests/pngstest-linear\
-   tests/pngstest-linear-alpha tests/pngstest-none tests/pngstest-none-alpha\
-   tests/pngstest-sRGB tests/pngstest-sRGB-alpha tests/pngunknown-IDAT\
-   tests/pngunknown-discard tests/pngunknown-if-safe tests/pngunknown-sAPI\
-   tests/pngunknown-sTER tests/pngunknown-save tests/pngunknown-vpAg\
-   tests/pngimage-quick tests/pngimage-full
+   tests/pngvalid-progressive-standard\
+   tests/pngvalid-standard\
+   tests/pngstest-1.8\
+   tests/pngstest-1.8-alpha\
+   tests/pngstest-linear\
+   tests/pngstest-linear-alpha\
+   tests/pngstest-none\
+   tests/pngstest-none-alpha\
+   tests/pngstest-sRGB\
+   tests/pngstest-sRGB-alpha\
+   tests/pngstest-large-stride\
+   tests/pngstest-negative-stride\
+   tests/pngstest-negative-stride-extra\
+   tests/pngunknown-IDAT\
+   tests/pngunknown-discard\
+   tests/pngunknown-if-safe\
+   tests/pngunknown-sAPI\
+   tests/pngunknown-sTER\
+   tests/pngunknown-save\
+   tests/pngunknown-vpAg\
+   tests/pngimage-quick\
+   tests/pngimage-full
 endif
 
 # man pages
@@ -235,9 +258,10 @@
 pngtest.o: pnglibconf.h
 
 contrib/libtests/makepng.o: pnglibconf.h
+contrib/libtests/pnggetset.o: pnglibconf.h
+contrib/libtests/pngimage.o: pnglibconf.h
 contrib/libtests/pngstest.o: pnglibconf.h
 contrib/libtests/pngunknown.o: pnglibconf.h
-contrib/libtests/pngimage.o: pnglibconf.h
 contrib/libtests/pngvalid.o: pnglibconf.h
 contrib/libtests/readpng.o: pnglibconf.h
 contrib/libtests/tarith.o: pnglibconf.h
diff --git a/README.md b/README.md
index 5e62d97..d2a9e71 100644
--- a/README.md
+++ b/README.md
@@ -24,14 +24,14 @@
 replacement for `fread()` and `fwrite()`, if you are so inclined.
 
 zlib should be available at the same place that libpng is, or at
-https://zlib.net .
+<https://zlib.net>.
 
 You may also want a copy of the PNG specification.  It is available
 as an RFC, a W3C Recommendation, and an ISO/IEC Standard.  You can find
-these at http://www.libpng.org/pub/png/pngdocs.html .
+these at <http://www.libpng.org/pub/png/pngdocs.html>.
 
-This code is currently being archived at https://libpng.sourceforge.io
-in the download area, and at http://libpng.download/src .
+This code is currently being archived at <https://libpng.sourceforge.io>
+in the download area, and at <http://libpng.download/src>.
 
 This release, based in a large way on Glenn's, Guy's and Andreas'
 earlier work, was created and will be supported by myself and the PNG
@@ -39,12 +39,12 @@
 
 Send comments, corrections and commendations to `png-mng-implement`
 at `lists.sourceforge.net`.  (Subscription is required; visit
-https://lists.sourceforge.net/lists/listinfo/png-mng-implement
+<https://lists.sourceforge.net/lists/listinfo/png-mng-implement>
 to subscribe.)
 
 Send general questions about the PNG specification to `png-mng-misc`
 at `lists.sourceforge.net`.  (Subscription is required; visit
-https://lists.sourceforge.net/lists/listinfo/png-mng-misc
+<https://lists.sourceforge.net/lists/listinfo/png-mng-misc>
 to subscribe.)
 
 Historical notes
diff --git a/REUSE.toml b/REUSE.toml
new file mode 100644
index 0000000..4f385e5
--- /dev/null
+++ b/REUSE.toml
@@ -0,0 +1,9 @@
+version = 1
+
+[[annotations]]
+SPDX-License-Identifier = "MIT"
+path = "ci/**"
+
+[[annotations]]
+SPDX-License-Identifier = "MIT-0"
+path = ".github/workflows/**"
diff --git a/TODO.md b/TODO.md
index 8ddb7d1..83b1237 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,5 +1,5 @@
 TODO list for libpng
---------------------
+====================
 
  * Fix all defects (duh!)
  * cHRM transformation.
diff --git a/arm/arm_init.c b/arm/arm_init.c
index 30f8b03..664d05d 100644
--- a/arm/arm_init.c
+++ b/arm/arm_init.c
@@ -99,7 +99,7 @@
     *    the original code in pngrtran.c  That code is now here.
     *
     * 3) The original code takes pointers to the end of the input and the end of
-    *    the output; this is the way png_do_expand_palette works becuase it
+    *    the output; this is the way png_do_expand_palette works because it
     *    has to copy down from the end (otherwise it would overwrite the input
     *    data before it read it).  Note that the row buffer is aliased by
     *    these two pointers.
@@ -186,7 +186,7 @@
          /* Finally update row_info to reflect the expanded output: */
          row_info->bit_depth = 8;
          row_info->pixel_depth = 32;
-         row_info->rowbytes = row_width * 4;
+         row_info->rowbytes = (size_t)row_width * 4;
          row_info->color_type = 6;
          row_info->channels = 4;
          return 1;
@@ -194,7 +194,7 @@
       else
       {
          /* No tRNS chunk (num_trans == 0), expand to RGB not RGBA. */
-         png_byte *dp = row + (3/*RGB*/*row_width - 1);
+         png_byte *dp = row + (3/*RGB*/ * (size_t)row_width - 1);
 
          png_uint_32 i = png_target_do_expand_palette_rgb8_neon(palette,
                row_info->width, &sp, &dp);
@@ -213,7 +213,7 @@
 
          row_info->bit_depth = 8;
          row_info->pixel_depth = 24;
-         row_info->rowbytes = row_width * 3;
+         row_info->rowbytes = (size_t)row_width * 3;
          row_info->color_type = 2;
          row_info->channels = 3;
          return 1;
diff --git a/arm/palette_neon_intrinsics.c b/arm/palette_neon_intrinsics.c
index e4f0af0..d5da6ae 100644
--- a/arm/palette_neon_intrinsics.c
+++ b/arm/palette_neon_intrinsics.c
@@ -1,6 +1,6 @@
 /* palette_neon_intrinsics.c - NEON optimised palette expansion functions
  *
- * Copyright (c) 2018-2019 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 2017-2018 Arm Holdings. All rights reserved.
  * Written by Richard Townsend <Richard.Townsend@arm.com>, February 2017.
  *
@@ -35,12 +35,12 @@
       w.val[0] = v.val[0];
       w.val[1] = v.val[1];
       w.val[2] = v.val[2];
-      vst4q_u8(riffled_palette + (i << 2), w);
+      vst4q_u8(riffled_palette + i * 4, w);
    }
 
    /* Fix up the missing transparency values. */
    for (i = 0; i < num_trans; i++)
-      riffled_palette[(i << 2) + 3] = trans_alpha[i];
+      riffled_palette[i * 4 + 3] = trans_alpha[i];
 }
 
 /* Expands a palettized row into RGBA8. */
@@ -60,28 +60,27 @@
     * The NEON part writes forward from a given position, so we have
     * to seek this back by 4 pixels x 4 bytes.
     */
-   *ddp = *ddp - ((pixels_per_chunk * sizeof(png_uint_32)) - 1);
+   *ddp = *ddp - (pixels_per_chunk * 4 - 1);
 
-   for (i = 0; i < row_width; i += pixels_per_chunk)
+   for (i = 0; i + pixels_per_chunk <= row_width; i += pixels_per_chunk)
    {
       uint32x4_t cur;
       const png_byte *sp = *ssp - i;
-      png_byte *dp = *ddp - (i << 2);
+      png_byte *dp = *ddp - i * 4;
       cur = vld1q_dup_u32 (riffled_palette + *(sp - 3));
       cur = vld1q_lane_u32(riffled_palette + *(sp - 2), cur, 1);
       cur = vld1q_lane_u32(riffled_palette + *(sp - 1), cur, 2);
       cur = vld1q_lane_u32(riffled_palette + *(sp - 0), cur, 3);
       vst1q_u32((void *)dp, cur);
    }
-   if (i != row_width)
-   {
-      /* Remove the amount that wasn't processed. */
-      i -= pixels_per_chunk;
-   }
 
-   /* Decrement output pointers. */
+   /* Undo the pre-adjustment of *ddp before the pointer handoff,
+    * so the scalar fallback in pngrtran.c receives a dp that points
+    * to the correct position.
+    */
+   *ddp = *ddp + (pixels_per_chunk * 4 - 1);
    *ssp = *ssp - i;
-   *ddp = *ddp - (i << 2);
+   *ddp = *ddp - i * 4;
    return i;
 }
 
@@ -102,32 +101,30 @@
       return 0;
 
    /* Seeking this back by 8 pixels x 3 bytes. */
-   *ddp = *ddp - ((pixels_per_chunk * sizeof(png_color)) - 1);
+   *ddp = *ddp - (pixels_per_chunk * 3 - 1);
 
-   for (i = 0; i < row_width; i += pixels_per_chunk)
+   for (i = 0; i + pixels_per_chunk <= row_width; i += pixels_per_chunk)
    {
       uint8x8x3_t cur;
       const png_byte *sp = *ssp - i;
-      png_byte *dp = *ddp - ((i << 1) + i);
-      cur = vld3_dup_u8(palette + sizeof(png_color) * (*(sp - 7)));
-      cur = vld3_lane_u8(palette + sizeof(png_color) * (*(sp - 6)), cur, 1);
-      cur = vld3_lane_u8(palette + sizeof(png_color) * (*(sp - 5)), cur, 2);
-      cur = vld3_lane_u8(palette + sizeof(png_color) * (*(sp - 4)), cur, 3);
-      cur = vld3_lane_u8(palette + sizeof(png_color) * (*(sp - 3)), cur, 4);
-      cur = vld3_lane_u8(palette + sizeof(png_color) * (*(sp - 2)), cur, 5);
-      cur = vld3_lane_u8(palette + sizeof(png_color) * (*(sp - 1)), cur, 6);
-      cur = vld3_lane_u8(palette + sizeof(png_color) * (*(sp - 0)), cur, 7);
+      const png_byte *dp = *ddp - i * 3;
+      cur = vld3_dup_u8(palette + *(sp - 7) * 3);
+      cur = vld3_lane_u8(palette + *(sp - 6) * 3, cur, 1);
+      cur = vld3_lane_u8(palette + *(sp - 5) * 3, cur, 2);
+      cur = vld3_lane_u8(palette + *(sp - 4) * 3, cur, 3);
+      cur = vld3_lane_u8(palette + *(sp - 3) * 3, cur, 4);
+      cur = vld3_lane_u8(palette + *(sp - 2) * 3, cur, 5);
+      cur = vld3_lane_u8(palette + *(sp - 1) * 3, cur, 6);
+      cur = vld3_lane_u8(palette + *(sp - 0) * 3, cur, 7);
       vst3_u8((void *)dp, cur);
    }
 
-   if (i != row_width)
-   {
-      /* Remove the amount that wasn't processed. */
-      i -= pixels_per_chunk;
-   }
-
-   /* Decrement output pointers. */
+   /* Undo the pre-adjustment of *ddp before the pointer handoff,
+    * so the scalar fallback in pngrtran.c receives a dp that points
+    * to the correct position.
+    */
+   *ddp = *ddp + (pixels_per_chunk * 3 - 1);
    *ssp = *ssp - i;
-   *ddp = *ddp - ((i << 1) + i);
+   *ddp = *ddp - i * 3;
    return i;
 }
diff --git a/ci/.shellcheckrc b/ci/.shellcheckrc
index 3c035d6..da7fd52 100644
--- a/ci/.shellcheckrc
+++ b/ci/.shellcheckrc
@@ -1,3 +1,6 @@
+# Copyright (C) 2023 Cosmin Truta
+# SPDX-License-Identifier: MIT
+
 # Disable the "expressions don't expand in single quotes, use double quotes"
 # advice. We need the regular expressions to remain uninterpolated.
 disable=SC2016
diff --git a/ci/README.md b/ci/README.md
index bb032ed..d11c14d 100644
--- a/ci/README.md
+++ b/ci/README.md
@@ -4,22 +4,21 @@
 Copyright Notice
 ----------------
 
-Copyright (c) 2019-2025 Cosmin Truta.
+Copyright (C) 2019 Cosmin Truta.
 
 Use, modification and distribution are subject to the MIT License.
-Please see the accompanying file `LICENSE_MIT.txt` or visit
-https://opensource.org/license/mit
+Please see the accompanying file `LICENSES/MIT.txt` or visit
+<https://opensource.org/license/mit>
 
 File List
 ---------
 
-    LICENSE_MIT.txt         ==>  The License file
     README.md               ==>  This file
     ci_lint.sh              ==>  Lint the source code
-    ci_shellify.sh          ==>  Convert select definitions to shell syntax
     ci_verify_cmake.sh      ==>  Verify the build driven by CMakeLists.txt
     ci_verify_configure.sh  ==>  Verify the build driven by configure
     ci_verify_makefiles.sh  ==>  Verify the build driven by scripts/makefile.*
     ci_verify_version.sh    ==>  Verify the consistency of version definitions
-    lib/ci.lib.sh           ==>  Shell utilities for the main ci_*.sh scripts
+    lib/ci.lib.sh           ==>  Shell library for the main ci_*.sh scripts
+    libexec/ci_*.sh         ==>  Shell utilities for the main ci_*.sh scripts
     targets/*/ci_env.*.sh   ==>  Shell environments for cross-platform testing
diff --git a/ci/ci_lint.sh b/ci/ci_lint.sh
index 674e55f..c1e1550 100755
--- a/ci/ci_lint.sh
+++ b/ci/ci_lint.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/ci_verify_cmake.sh b/ci/ci_verify_cmake.sh
index aa06461..adb7eb6 100755
--- a/ci/ci_verify_cmake.sh
+++ b/ci/ci_verify_cmake.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/ci_verify_configure.sh b/ci/ci_verify_configure.sh
index b0c74df..2f986eb 100755
--- a/ci/ci_verify_configure.sh
+++ b/ci/ci_verify_configure.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/ci_verify_makefiles.sh b/ci/ci_verify_makefiles.sh
index 2876a86..412a79d 100755
--- a/ci/ci_verify_makefiles.sh
+++ b/ci/ci_verify_makefiles.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/ci_verify_version.sh b/ci/ci_verify_version.sh
index 5908724..31b5889 100755
--- a/ci/ci_verify_version.sh
+++ b/ci/ci_verify_version.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/lib/ci.lib.sh b/ci/lib/ci.lib.sh
index 7a7474a..1bc5b4e 100644
--- a/ci/lib/ci.lib.sh
+++ b/ci/lib/ci.lib.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 test -f "$BASH_SOURCE" || {
diff --git a/ci/libexec/ci_shellify_autoconf.sh b/ci/libexec/ci_shellify_autoconf.sh
index bf37f0d..d251b9d 100755
--- a/ci/libexec/ci_shellify_autoconf.sh
+++ b/ci/libexec/ci_shellify_autoconf.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/libexec/ci_shellify_c.sh b/ci/libexec/ci_shellify_c.sh
index 244fc22..dacc29e 100755
--- a/ci/libexec/ci_shellify_c.sh
+++ b/ci/libexec/ci_shellify_c.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/libexec/ci_shellify_cmake.sh b/ci/libexec/ci_shellify_cmake.sh
index 5596241..389c230 100755
--- a/ci/libexec/ci_shellify_cmake.sh
+++ b/ci/libexec/ci_shellify_cmake.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/libexec/ci_shellify_shell.sh b/ci/libexec/ci_shellify_shell.sh
index 4170063..17fdeec 100755
--- a/ci/libexec/ci_shellify_shell.sh
+++ b/ci/libexec/ci_shellify_shell.sh
@@ -1,11 +1,7 @@
 #!/usr/bin/env bash
 set -o errexit -o pipefail -o posix
 
-# Copyright (c) 2019-2025 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2019 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 # shellcheck source=ci/lib/ci.lib.sh
diff --git a/ci/targets/android/ci_env.aarch64-linux-android.sh b/ci/targets/android/ci_env.aarch64-linux-android.sh
index fef0ef1..7d5b33e 100644
--- a/ci/targets/android/ci_env.aarch64-linux-android.sh
+++ b/ci/targets/android/ci_env.aarch64-linux-android.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=aarch64
diff --git a/ci/targets/android/ci_env.armv7a-linux-androideabi.sh b/ci/targets/android/ci_env.armv7a-linux-androideabi.sh
index c27bd12..bf19aa8 100644
--- a/ci/targets/android/ci_env.armv7a-linux-androideabi.sh
+++ b/ci/targets/android/ci_env.armv7a-linux-androideabi.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=arm
diff --git a/ci/targets/android/ci_env.i686-linux-android.sh b/ci/targets/android/ci_env.i686-linux-android.sh
index 88e3690..2268ae7 100644
--- a/ci/targets/android/ci_env.i686-linux-android.sh
+++ b/ci/targets/android/ci_env.i686-linux-android.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i686
diff --git a/ci/targets/android/ci_env.x86_64-linux-android.sh b/ci/targets/android/ci_env.x86_64-linux-android.sh
index 87460c8..aaf1038 100644
--- a/ci/targets/android/ci_env.x86_64-linux-android.sh
+++ b/ci/targets/android/ci_env.x86_64-linux-android.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=x86_64
diff --git a/ci/targets/cygwin/ci_env.i686-pc-cygwin.sh b/ci/targets/cygwin/ci_env.i686-pc-cygwin.sh
index 66b9999..d5ee8df 100644
--- a/ci/targets/cygwin/ci_env.i686-pc-cygwin.sh
+++ b/ci/targets/cygwin/ci_env.i686-pc-cygwin.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i686
diff --git a/ci/targets/cygwin/ci_env.x86_64-pc-cygwin.sh b/ci/targets/cygwin/ci_env.x86_64-pc-cygwin.sh
index 78f8c25..1f7defa 100644
--- a/ci/targets/cygwin/ci_env.x86_64-pc-cygwin.sh
+++ b/ci/targets/cygwin/ci_env.x86_64-pc-cygwin.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=x86_64
diff --git a/ci/targets/darwin/ci_env.arm64-apple-darwin.sh b/ci/targets/darwin/ci_env.arm64-apple-darwin.sh
index c54d8c7..b8913e9 100644
--- a/ci/targets/darwin/ci_env.arm64-apple-darwin.sh
+++ b/ci/targets/darwin/ci_env.arm64-apple-darwin.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=arm64
diff --git a/ci/targets/darwin/ci_env.x86_64-apple-darwin.sh b/ci/targets/darwin/ci_env.x86_64-apple-darwin.sh
index ee87711..b04a0fa 100644
--- a/ci/targets/darwin/ci_env.x86_64-apple-darwin.sh
+++ b/ci/targets/darwin/ci_env.x86_64-apple-darwin.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=x86_64
diff --git a/ci/targets/freebsd/ci_env.aarch64-unknown-freebsd.sh b/ci/targets/freebsd/ci_env.aarch64-unknown-freebsd.sh
index 42235de..58b6707 100644
--- a/ci/targets/freebsd/ci_env.aarch64-unknown-freebsd.sh
+++ b/ci/targets/freebsd/ci_env.aarch64-unknown-freebsd.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=aarch64
diff --git a/ci/targets/freebsd/ci_env.i686-unknown-freebsd.sh b/ci/targets/freebsd/ci_env.i686-unknown-freebsd.sh
index 3d188f8..0fdfa5c 100644
--- a/ci/targets/freebsd/ci_env.i686-unknown-freebsd.sh
+++ b/ci/targets/freebsd/ci_env.i686-unknown-freebsd.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i686
diff --git a/ci/targets/freebsd/ci_env.riscv64-unknown-freebsd.sh b/ci/targets/freebsd/ci_env.riscv64-unknown-freebsd.sh
index 0a02cde..6365b69 100644
--- a/ci/targets/freebsd/ci_env.riscv64-unknown-freebsd.sh
+++ b/ci/targets/freebsd/ci_env.riscv64-unknown-freebsd.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=riscv64
diff --git a/ci/targets/freebsd/ci_env.x86_64-unknown-freebsd.sh b/ci/targets/freebsd/ci_env.x86_64-unknown-freebsd.sh
index c77ace5..cdc7aea 100644
--- a/ci/targets/freebsd/ci_env.x86_64-unknown-freebsd.sh
+++ b/ci/targets/freebsd/ci_env.x86_64-unknown-freebsd.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=x86_64
diff --git a/ci/targets/linux/ci_env.aarch64-linux-gnu.sh b/ci/targets/linux/ci_env.aarch64-linux-gnu.sh
index cb85bc6..a185d68 100644
--- a/ci/targets/linux/ci_env.aarch64-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.aarch64-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=aarch64
diff --git a/ci/targets/linux/ci_env.arm-linux-gnueabi.sh b/ci/targets/linux/ci_env.arm-linux-gnueabi.sh
index 45504df..c8ec561 100644
--- a/ci/targets/linux/ci_env.arm-linux-gnueabi.sh
+++ b/ci/targets/linux/ci_env.arm-linux-gnueabi.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=arm
diff --git a/ci/targets/linux/ci_env.arm-linux-gnueabihf.sh b/ci/targets/linux/ci_env.arm-linux-gnueabihf.sh
index 3eb9d18..30f767c 100644
--- a/ci/targets/linux/ci_env.arm-linux-gnueabihf.sh
+++ b/ci/targets/linux/ci_env.arm-linux-gnueabihf.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=arm
diff --git a/ci/targets/linux/ci_env.i686-linux-gnu.sh b/ci/targets/linux/ci_env.i686-linux-gnu.sh
index a5efd9f..324676a 100644
--- a/ci/targets/linux/ci_env.i686-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.i686-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i686
diff --git a/ci/targets/linux/ci_env.mips-linux-gnu.sh b/ci/targets/linux/ci_env.mips-linux-gnu.sh
index 532c93c..d5fc2cc 100644
--- a/ci/targets/linux/ci_env.mips-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.mips-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=mips
diff --git a/ci/targets/linux/ci_env.mips64-linux-gnuabi64.sh b/ci/targets/linux/ci_env.mips64-linux-gnuabi64.sh
index 348d2b8..68e2b98 100644
--- a/ci/targets/linux/ci_env.mips64-linux-gnuabi64.sh
+++ b/ci/targets/linux/ci_env.mips64-linux-gnuabi64.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=mips64
diff --git a/ci/targets/linux/ci_env.mips64el-linux-gnuabi64.sh b/ci/targets/linux/ci_env.mips64el-linux-gnuabi64.sh
index e264913..f8ab18f 100644
--- a/ci/targets/linux/ci_env.mips64el-linux-gnuabi64.sh
+++ b/ci/targets/linux/ci_env.mips64el-linux-gnuabi64.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=mips64el
diff --git a/ci/targets/linux/ci_env.mipsel-linux-gnu.sh b/ci/targets/linux/ci_env.mipsel-linux-gnu.sh
index f99050f..68688c5 100644
--- a/ci/targets/linux/ci_env.mipsel-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.mipsel-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=mipsel
diff --git a/ci/targets/linux/ci_env.mipsisa32r6-linux-gnu.sh b/ci/targets/linux/ci_env.mipsisa32r6-linux-gnu.sh
index 0a32867..d3443c0 100644
--- a/ci/targets/linux/ci_env.mipsisa32r6-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.mipsisa32r6-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=mipsisa32r6
diff --git a/ci/targets/linux/ci_env.mipsisa32r6el-linux-gnu.sh b/ci/targets/linux/ci_env.mipsisa32r6el-linux-gnu.sh
index ca06009..c534f73 100644
--- a/ci/targets/linux/ci_env.mipsisa32r6el-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.mipsisa32r6el-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=mipsisa32r6el
diff --git a/ci/targets/linux/ci_env.mipsisa64r6-linux-gnuabi64.sh b/ci/targets/linux/ci_env.mipsisa64r6-linux-gnuabi64.sh
index 6c1138f..bd1ddb8 100644
--- a/ci/targets/linux/ci_env.mipsisa64r6-linux-gnuabi64.sh
+++ b/ci/targets/linux/ci_env.mipsisa64r6-linux-gnuabi64.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=mipsisa64r6
diff --git a/ci/targets/linux/ci_env.mipsisa64r6el-linux-gnuabi64.sh b/ci/targets/linux/ci_env.mipsisa64r6el-linux-gnuabi64.sh
index f64f2fc..9101097 100644
--- a/ci/targets/linux/ci_env.mipsisa64r6el-linux-gnuabi64.sh
+++ b/ci/targets/linux/ci_env.mipsisa64r6el-linux-gnuabi64.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=mipsisa64r6el
diff --git a/ci/targets/linux/ci_env.powerpc-linux-gnu.sh b/ci/targets/linux/ci_env.powerpc-linux-gnu.sh
index e50d9b5..43d676a 100644
--- a/ci/targets/linux/ci_env.powerpc-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.powerpc-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=powerpc
diff --git a/ci/targets/linux/ci_env.powerpc64-linux-gnu.sh b/ci/targets/linux/ci_env.powerpc64-linux-gnu.sh
index 15e60ad..cb9cdda 100644
--- a/ci/targets/linux/ci_env.powerpc64-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.powerpc64-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=powerpc64
diff --git a/ci/targets/linux/ci_env.powerpc64le-linux-gnu.sh b/ci/targets/linux/ci_env.powerpc64le-linux-gnu.sh
index be0e2ca..d254e01 100644
--- a/ci/targets/linux/ci_env.powerpc64le-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.powerpc64le-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=powerpc64le
diff --git a/ci/targets/linux/ci_env.riscv64-linux-gnu.sh b/ci/targets/linux/ci_env.riscv64-linux-gnu.sh
index d8518d9..9b35104 100644
--- a/ci/targets/linux/ci_env.riscv64-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.riscv64-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=riscv64
diff --git a/ci/targets/linux/ci_env.x86_64-linux-gnu.sh b/ci/targets/linux/ci_env.x86_64-linux-gnu.sh
index 3263fbf..d8bca7b 100644
--- a/ci/targets/linux/ci_env.x86_64-linux-gnu.sh
+++ b/ci/targets/linux/ci_env.x86_64-linux-gnu.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=x86_64
diff --git a/ci/targets/msdos/ci_env.i386-pc-msdoswatcom.sh b/ci/targets/msdos/ci_env.i386-pc-msdoswatcom.sh
index 59f3bd5..9bdcc27 100644
--- a/ci/targets/msdos/ci_env.i386-pc-msdoswatcom.sh
+++ b/ci/targets/msdos/ci_env.i386-pc-msdoswatcom.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i386
diff --git a/ci/targets/msdos/ci_env.i586-pc-msdosdjgpp.sh b/ci/targets/msdos/ci_env.i586-pc-msdosdjgpp.sh
index 63e6d06..9ae66bf 100644
--- a/ci/targets/msdos/ci_env.i586-pc-msdosdjgpp.sh
+++ b/ci/targets/msdos/ci_env.i586-pc-msdosdjgpp.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i586
diff --git a/ci/targets/msdos/ci_env.i86-pc-msdoswatcom.sh b/ci/targets/msdos/ci_env.i86-pc-msdoswatcom.sh
index 3059f18..97f9dcf 100644
--- a/ci/targets/msdos/ci_env.i86-pc-msdoswatcom.sh
+++ b/ci/targets/msdos/ci_env.i86-pc-msdoswatcom.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i86
diff --git a/ci/targets/windows/ci_env.aarch64-windows-llvm.sh b/ci/targets/windows/ci_env.aarch64-windows-llvm.sh
index 8024417..5f35845 100644
--- a/ci/targets/windows/ci_env.aarch64-windows-llvm.sh
+++ b/ci/targets/windows/ci_env.aarch64-windows-llvm.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=aarch64
diff --git a/ci/targets/windows/ci_env.i686-w64-mingw32.sh b/ci/targets/windows/ci_env.i686-w64-mingw32.sh
index 8c83d0f..825881a 100644
--- a/ci/targets/windows/ci_env.i686-w64-mingw32.sh
+++ b/ci/targets/windows/ci_env.i686-w64-mingw32.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i686
diff --git a/ci/targets/windows/ci_env.i686-windows-llvm.sh b/ci/targets/windows/ci_env.i686-windows-llvm.sh
index 3d29f6d..c54a113 100644
--- a/ci/targets/windows/ci_env.i686-windows-llvm.sh
+++ b/ci/targets/windows/ci_env.i686-windows-llvm.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=i686
diff --git a/ci/targets/windows/ci_env.x86_64-w64-mingw32.sh b/ci/targets/windows/ci_env.x86_64-w64-mingw32.sh
index 67d8355..bb0dfc2 100644
--- a/ci/targets/windows/ci_env.x86_64-w64-mingw32.sh
+++ b/ci/targets/windows/ci_env.x86_64-w64-mingw32.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=x86_64
diff --git a/ci/targets/windows/ci_env.x86_64-windows-llvm.sh b/ci/targets/windows/ci_env.x86_64-windows-llvm.sh
index 747f99b..f543fcf 100644
--- a/ci/targets/windows/ci_env.x86_64-windows-llvm.sh
+++ b/ci/targets/windows/ci_env.x86_64-windows-llvm.sh
@@ -1,8 +1,4 @@
-# Copyright (c) 2023-2024 Cosmin Truta.
-#
-# Use, modification and distribution are subject to the MIT License.
-# Please see the accompanying file LICENSE_MIT.txt
-#
+# Copyright (C) 2023 Cosmin Truta
 # SPDX-License-Identifier: MIT
 
 export CI_TARGET_ARCH=x86_64
diff --git a/configure.ac b/configure.ac
index f6d3fc7..6b3fa95 100644
--- a/configure.ac
+++ b/configure.ac
@@ -174,6 +174,19 @@
   [AC_CHECK_LIB([z], [${ZPREFIX}zlibVersion], ,
      [AC_MSG_ERROR([zlib not installed])])])
 
+AC_MSG_CHECKING([zlib version])
+AC_COMPILE_IFELSE(
+  [AC_LANG_SOURCE([
+#include <zlib.h>
+#if !defined(ZLIB_VERNUM) || ZLIB_VERNUM < 0x1280
+#error zlib version 1.2.8 or later is required
+#endif
+int main(void) { return 0; }
+])],
+  [AC_MSG_RESULT([acceptable])],
+  [AC_MSG_RESULT([too old or ZLIB_VERNUM not defined])
+   AC_MSG_ERROR([zlib version 1.2.8 or later is required])])
+
 # The following is for pngvalid, to ensure it catches FP errors even on
 # platforms that don't enable FP exceptions, the function appears in the math
 # library (typically), it's not an error if it is not found.
diff --git a/contrib/arm-neon/README b/contrib/arm-neon/README
index b4248cf..862f702 100644
--- a/contrib/arm-neon/README
+++ b/contrib/arm-neon/README
@@ -46,7 +46,7 @@
 The file is a fragment of C code. It should not define any 'extern' symbols;
 everything should be static.  It must define the function:
 
-static int png_have_neon(png_structp png_ptr);
+static int png_have_neon(png_struct *png_ptr);
 
 That function must return 1 if ARM NEON instructions are supported, 0 if not.
 It must not execute png_error unless it detects a bug.  A png_error will prevent
diff --git a/contrib/libtests/pnggetset.c b/contrib/libtests/pnggetset.c
new file mode 100644
index 0000000..e2c1ca5
--- /dev/null
+++ b/contrib/libtests/pnggetset.c
@@ -0,0 +1,862 @@
+/* pnggetset.c
+ *
+ * Copyright (c) 2026 Cosmin Truta
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * Test getter and setter correctness.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H)
+#  include <config.h>
+#endif
+
+#ifdef PNG_FREESTANDING_TESTS
+#  include <png.h>
+#else
+#  include "../../png.h"
+#endif
+
+/* Test: get the PLTE, pass it straight back to set, verify roundtrip. */
+static int
+test_plte_roundtrip(void)
+{
+   png_structp png_ptr;
+   png_infop info_ptr;
+   png_color palette[4];
+   png_colorp got_palette = NULL;
+   int num_palette = 0;
+   int i;
+
+   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+       NULL, NULL, NULL);
+   if (png_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_write_struct failed\n");
+      return 1;
+   }
+
+   info_ptr = png_create_info_struct(png_ptr);
+   if (info_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_info_struct failed\n");
+      png_destroy_write_struct(&png_ptr, NULL);
+      return 1;
+   }
+
+   if (setjmp(png_jmpbuf(png_ptr)))
+   {
+      fprintf(stderr, "pnggetset: libpng error in test_plte_roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* Set up a palette-color image header. */
+   png_set_IHDR(png_ptr, info_ptr, 1, 1, 8, PNG_COLOR_TYPE_PALETTE,
+       PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+   /* Populate with recognizable values. */
+   for (i = 0; i < 4; i++)
+   {
+      palette[i].red   = (png_byte)(i * 10);
+      palette[i].green = (png_byte)(i * 20);
+      palette[i].blue  = (png_byte)(i * 30);
+   }
+   png_set_PLTE(png_ptr, info_ptr, palette, 4);
+
+   /* Get the internal pointer and feed it straight back. */
+   png_get_PLTE(png_ptr, info_ptr, &got_palette, &num_palette);
+   if (got_palette == NULL || num_palette != 4)
+   {
+      fprintf(stderr, "pnggetset: png_get_PLTE returned unexpected values\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* This is the critical call: the pointer aliases info_ptr->palette. */
+   png_set_PLTE(png_ptr, info_ptr, got_palette, num_palette);
+
+   /* Verify the data survived the roundtrip. */
+   got_palette = NULL;
+   num_palette = 0;
+   png_get_PLTE(png_ptr, info_ptr, &got_palette, &num_palette);
+   if (got_palette == NULL || num_palette != 4)
+   {
+      fprintf(stderr, "pnggetset: PLTE lost after roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+   for (i = 0; i < 4; i++)
+   {
+      if ((got_palette[i].red != (png_byte)(i * 10))
+          || (got_palette[i].green != (png_byte)(i * 20))
+          || (got_palette[i].blue != (png_byte)(i * 30)))
+      {
+         fprintf(stderr,
+             "pnggetset: PLTE entry %d corrupted after roundtrip\n", i);
+         png_destroy_write_struct(&png_ptr, &info_ptr);
+         return 1;
+      }
+   }
+
+   png_destroy_write_struct(&png_ptr, &info_ptr);
+   return 0;
+}
+
+#ifdef PNG_hIST_SUPPORTED
+/* Test: get the hIST, pass it straight back to set, verify roundtrip. */
+static int
+test_hist_roundtrip(void)
+{
+   png_structp png_ptr;
+   png_infop info_ptr;
+   png_color palette[4];
+   png_uint_16 hist[4];
+   png_uint_16p got_hist = NULL;
+   int i;
+
+   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+       NULL, NULL, NULL);
+   if (png_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_write_struct failed\n");
+      return 1;
+   }
+
+   info_ptr = png_create_info_struct(png_ptr);
+   if (info_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_info_struct failed\n");
+      png_destroy_write_struct(&png_ptr, NULL);
+      return 1;
+   }
+
+   if (setjmp(png_jmpbuf(png_ptr)))
+   {
+      fprintf(stderr, "pnggetset: libpng error in test_hist_roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* Set up a palette-color image header. */
+   memset(palette, 0, sizeof palette);
+   png_set_IHDR(png_ptr, info_ptr, 1, 1, 8, PNG_COLOR_TYPE_PALETTE,
+       PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+   png_set_PLTE(png_ptr, info_ptr, palette, 4);
+
+   /* Populate with recognizable values. */
+   for (i = 0; i < 4; i++)
+      hist[i] = (png_uint_16)(i * 100 + 42);
+
+   png_set_hIST(png_ptr, info_ptr, hist);
+
+   /* Get the internal pointer and feed it straight back. */
+   if (png_get_hIST(png_ptr, info_ptr, &got_hist) == 0 || got_hist == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_get_hIST returned unexpected values\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* This is the critical call: the pointer aliases info_ptr->hist. */
+   png_set_hIST(png_ptr, info_ptr, got_hist);
+
+   /* Verify the data survived the roundtrip. */
+   got_hist = NULL;
+   if (png_get_hIST(png_ptr, info_ptr, &got_hist) == 0 || got_hist == NULL)
+   {
+      fprintf(stderr, "pnggetset: hIST lost after roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+   for (i = 0; i < 4; i++)
+   {
+      if (got_hist[i] != (png_uint_16)(i * 100 + 42))
+      {
+         fprintf(stderr,
+             "pnggetset: hIST entry %d corrupted after roundtrip\n", i);
+         png_destroy_write_struct(&png_ptr, &info_ptr);
+         return 1;
+      }
+   }
+
+   png_destroy_write_struct(&png_ptr, &info_ptr);
+   return 0;
+}
+#endif /* PNG_hIST_SUPPORTED */
+
+#ifdef PNG_tRNS_SUPPORTED
+/* Test: get the tRNS, pass it straight back to set, verify roundtrip. */
+static int
+test_trns_roundtrip(void)
+{
+   png_structp png_ptr;
+   png_infop info_ptr;
+   png_color palette[4];
+   png_byte trans_alpha[4];
+   png_color_16 trans_color;
+   png_bytep got_alpha = NULL;
+   png_color_16p got_color = NULL;
+   int num_trans = 0;
+   int i;
+
+   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+       NULL, NULL, NULL);
+   if (png_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_write_struct failed\n");
+      return 1;
+   }
+
+   info_ptr = png_create_info_struct(png_ptr);
+   if (info_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_info_struct failed\n");
+      png_destroy_write_struct(&png_ptr, NULL);
+      return 1;
+   }
+
+   if (setjmp(png_jmpbuf(png_ptr)))
+   {
+      fprintf(stderr, "pnggetset: libpng error in test_trns_roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* Set up a palette-color image. */
+   memset(palette, 0, sizeof palette);
+   png_set_IHDR(png_ptr, info_ptr, 1, 1, 8, PNG_COLOR_TYPE_PALETTE,
+       PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+   png_set_PLTE(png_ptr, info_ptr, palette, 4);
+
+   /* Populate tRNS with recognizable values. */
+   for (i = 0; i < 4; i++)
+      trans_alpha[i] = (png_byte)(0xff - i * 0x11);
+   memset(&trans_color, 0, sizeof trans_color);
+
+   png_set_tRNS(png_ptr, info_ptr, trans_alpha, 4, &trans_color);
+
+   /* Get the internal pointer and feed it straight back. */
+   png_get_tRNS(png_ptr, info_ptr, &got_alpha, &num_trans, &got_color);
+   if (got_alpha == NULL || num_trans != 4)
+   {
+      fprintf(stderr, "pnggetset: png_get_tRNS returned unexpected values\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* This is the critical call: the pointer aliases info_ptr->trans_alpha. */
+   png_set_tRNS(png_ptr, info_ptr, got_alpha, num_trans, got_color);
+
+   /* Verify the data survived the roundtrip. */
+   got_alpha = NULL;
+   num_trans = 0;
+   png_get_tRNS(png_ptr, info_ptr, &got_alpha, &num_trans, &got_color);
+   if (got_alpha == NULL || num_trans != 4)
+   {
+      fprintf(stderr, "pnggetset: tRNS lost after roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+   for (i = 0; i < 4; i++)
+   {
+      if (got_alpha[i] != (png_byte)(0xff - i * 0x11))
+      {
+         fprintf(stderr,
+             "pnggetset: tRNS entry %d corrupted after roundtrip\n", i);
+         png_destroy_write_struct(&png_ptr, &info_ptr);
+         return 1;
+      }
+   }
+
+   png_destroy_write_struct(&png_ptr, &info_ptr);
+   return 0;
+}
+#endif /* PNG_tRNS_SUPPORTED */
+
+#ifdef PNG_TEXT_SUPPORTED
+/* Test: get the text array, pass it straight back to set, verify data. */
+#define TEXT_COUNT 6 /* enough to trigger reallocation on the second set */
+static int
+test_text_roundtrip(void)
+{
+   png_structp png_ptr;
+   png_infop info_ptr;
+   png_text text_entries[TEXT_COUNT];
+   png_textp got_text = NULL;
+   int got_num_text = 0;
+   int i;
+
+   /* Recognizable keys and values. */
+   static const char *keys[TEXT_COUNT] = {
+      "Title", "Author", "Desc", "Copyright", "Source", "Comment"
+   };
+   static const char *vals[TEXT_COUNT] = {
+      "t0", "t1", "t2", "t3", "t4", "t5"
+   };
+
+   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+       NULL, NULL, NULL);
+   if (png_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_write_struct failed\n");
+      return 1;
+   }
+
+   info_ptr = png_create_info_struct(png_ptr);
+   if (info_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_info_struct failed\n");
+      png_destroy_write_struct(&png_ptr, NULL);
+      return 1;
+   }
+
+   if (setjmp(png_jmpbuf(png_ptr)))
+   {
+      fprintf(stderr, "pnggetset: libpng error in test_text_roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* Populate the text entries. */
+   memset(text_entries, 0, sizeof text_entries);
+   for (i = 0; i < TEXT_COUNT; i++)
+   {
+      text_entries[i].compression = PNG_TEXT_COMPRESSION_NONE;
+      text_entries[i].key = (png_charp)keys[i];
+      text_entries[i].text = (png_charp)vals[i];
+   }
+   png_set_text(png_ptr, info_ptr, text_entries, TEXT_COUNT);
+
+   /* Get the internal pointer and feed it straight back (append). */
+   png_get_text(png_ptr, info_ptr, &got_text, &got_num_text);
+   if (got_text == NULL || got_num_text != TEXT_COUNT)
+   {
+      fprintf(stderr, "pnggetset: png_get_text returned unexpected values\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* This is the critical call: got_text aliases info_ptr->text. */
+   png_set_text(png_ptr, info_ptr, got_text, got_num_text);
+
+   /* Verify the original entries survived. */
+   got_text = NULL;
+   got_num_text = 0;
+   png_get_text(png_ptr, info_ptr, &got_text, &got_num_text);
+   if (got_text == NULL || got_num_text != TEXT_COUNT * 2)
+   {
+      fprintf(stderr, "pnggetset: text count %d, expected %d after roundtrip\n",
+          got_num_text, TEXT_COUNT * 2);
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+   for (i = 0; i < TEXT_COUNT; i++)
+   {
+      if (got_text[i].key == NULL ||
+          strcmp(got_text[i].key, keys[i]) != 0 ||
+          got_text[i].text == NULL ||
+          strcmp(got_text[i].text, vals[i]) != 0)
+      {
+         fprintf(stderr,
+             "pnggetset: text entry %d corrupted after roundtrip\n", i);
+         png_destroy_write_struct(&png_ptr, &info_ptr);
+         return 1;
+      }
+   }
+
+   png_destroy_write_struct(&png_ptr, &info_ptr);
+   return 0;
+}
+#undef TEXT_COUNT
+#endif /* PNG_TEXT_SUPPORTED */
+
+#ifdef PNG_sPLT_SUPPORTED
+/* Test: get the sPLT array, pass it straight back to set, verify data. */
+static int
+test_splt_roundtrip(void)
+{
+   png_structp png_ptr;
+   png_infop info_ptr;
+   png_sPLT_t splt;
+   png_sPLT_entry splt_entries[4];
+   png_sPLT_tp got_spalettes = NULL;
+   int got_num, i;
+
+   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+       NULL, NULL, NULL);
+   if (png_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_write_struct failed\n");
+      return 1;
+   }
+
+   info_ptr = png_create_info_struct(png_ptr);
+   if (info_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_info_struct failed\n");
+      png_destroy_write_struct(&png_ptr, NULL);
+      return 1;
+   }
+
+   if (setjmp(png_jmpbuf(png_ptr)))
+   {
+      fprintf(stderr, "pnggetset: libpng error in test_splt_roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* Populate with recognizable values. */
+   memset(splt_entries, 0, sizeof splt_entries);
+   for (i = 0; i < 4; i++)
+   {
+      splt_entries[i].red = (png_uint_16)(i * 1000);
+      splt_entries[i].green = (png_uint_16)(i * 2000);
+      splt_entries[i].blue = (png_uint_16)(i * 3000);
+      splt_entries[i].alpha = 0xffffU;
+      splt_entries[i].frequency = (png_uint_16)(i + 1);
+   }
+   memset(&splt, 0, sizeof splt);
+   splt.name = (png_charp)"test_sPLT";
+   splt.depth = 16;
+   splt.entries = splt_entries;
+   splt.nentries = 4;
+
+   png_set_sPLT(png_ptr, info_ptr, &splt, 1);
+
+   /* Get the internal pointer and feed it straight back (append). */
+   got_num = png_get_sPLT(png_ptr, info_ptr, &got_spalettes);
+   if (got_spalettes == NULL || got_num != 1)
+   {
+      fprintf(stderr, "pnggetset: png_get_sPLT returned unexpected values\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* This is the critical call: got_spalettes aliases internal storage. */
+   png_set_sPLT(png_ptr, info_ptr, got_spalettes, got_num);
+
+   /* Verify the original entry survived. */
+   got_spalettes = NULL;
+   got_num = png_get_sPLT(png_ptr, info_ptr, &got_spalettes);
+   if (got_spalettes == NULL || got_num != 2)
+   {
+      fprintf(stderr, "pnggetset: sPLT count %d, expected 2 after roundtrip\n",
+          got_num);
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+   if (strcmp(got_spalettes[0].name, "test_sPLT") != 0 ||
+       got_spalettes[0].nentries != 4 ||
+       got_spalettes[0].depth != 16)
+   {
+      fprintf(stderr,
+          "pnggetset: sPLT entry 0 corrupted after roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+   for (i = 0; i < 4; i++)
+   {
+      if (got_spalettes[0].entries[i].red != (png_uint_16)(i * 1000) ||
+          got_spalettes[0].entries[i].green != (png_uint_16)(i * 2000) ||
+          got_spalettes[0].entries[i].blue != (png_uint_16)(i * 3000))
+      {
+         fprintf(stderr,
+             "pnggetset: sPLT[0] entry %d corrupted after roundtrip\n", i);
+         png_destroy_write_struct(&png_ptr, &info_ptr);
+         return 1;
+      }
+   }
+
+   png_destroy_write_struct(&png_ptr, &info_ptr);
+   return 0;
+}
+#endif /* PNG_sPLT_SUPPORTED */
+
+#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
+/* Test: get unknown chunks, pass them straight back to set, verify data. */
+static int
+test_unknown_roundtrip(void)
+{
+   png_structp png_ptr;
+   png_infop info_ptr;
+   png_unknown_chunk unk;
+   png_unknown_chunkp got_unknowns = NULL;
+   int got_num;
+   static const png_byte test_data[] = {0xde, 0xad, 0xbe, 0xef};
+
+   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+       NULL, NULL, NULL);
+   if (png_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_write_struct failed\n");
+      return 1;
+   }
+
+   info_ptr = png_create_info_struct(png_ptr);
+   if (info_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_info_struct failed\n");
+      png_destroy_write_struct(&png_ptr, NULL);
+      return 1;
+   }
+
+   if (setjmp(png_jmpbuf(png_ptr)))
+   {
+      fprintf(stderr,
+          "pnggetset: libpng error in test_unknown_roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* Set up an unknown chunk with recognizable data. */
+   memset(&unk, 0, sizeof unk);
+   memcpy(unk.name, "teSt", 5);
+   unk.data = (png_bytep)test_data;
+   unk.size = sizeof test_data;
+   unk.location = PNG_HAVE_IHDR;
+
+   png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0);
+   png_set_unknown_chunks(png_ptr, info_ptr, &unk, 1);
+
+   /* Get the internal pointer and feed it straight back (append). */
+   got_num = png_get_unknown_chunks(png_ptr, info_ptr, &got_unknowns);
+   if (got_unknowns == NULL || got_num != 1)
+   {
+      fprintf(stderr,
+          "pnggetset: png_get_unknown_chunks returned unexpected values\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   /* This is the critical call: got_unknowns aliases internal storage. */
+   png_set_unknown_chunks(png_ptr, info_ptr, got_unknowns, got_num);
+
+   /* Verify the original entry survived. */
+   got_unknowns = NULL;
+   got_num = png_get_unknown_chunks(png_ptr, info_ptr, &got_unknowns);
+   if (got_unknowns == NULL || got_num != 2)
+   {
+      fprintf(stderr,
+          "pnggetset: unknown_chunks count %d, expected 2 after roundtrip\n",
+          got_num);
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+   if (memcmp(got_unknowns[0].name, "teSt", 4) != 0 ||
+       got_unknowns[0].size != sizeof test_data ||
+       memcmp(got_unknowns[0].data, test_data, sizeof test_data) != 0)
+   {
+      fprintf(stderr,
+          "pnggetset: unknown chunk 0 corrupted after roundtrip\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   png_destroy_write_struct(&png_ptr, &info_ptr);
+   return 0;
+}
+#endif /* PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED */
+
+/* Memory buffer for PNG I/O without temp files. */
+#define MEM_BUF_SIZE 4096
+
+typedef struct
+{
+   png_byte data[MEM_BUF_SIZE];
+   size_t len;
+   size_t pos;
+} mem_buf;
+
+static void PNGCBAPI
+mem_write(png_structp png_ptr, png_bytep buf, png_size_t length)
+{
+   mem_buf *mb = (mem_buf *)png_get_io_ptr(png_ptr);
+
+   if (mb->len + length > MEM_BUF_SIZE)
+      png_error(png_ptr, "pnggetset: write overflow");
+
+   memcpy(mb->data + mb->len, buf, length);
+   mb->len += length;
+}
+
+static void PNGCBAPI
+mem_flush(png_structp png_ptr)
+{
+   (void)png_ptr;
+}
+
+static void PNGCBAPI
+mem_read(png_structp png_ptr, png_bytep buf, png_size_t length)
+{
+   mem_buf *mb = (mem_buf *)png_get_io_ptr(png_ptr);
+
+   if (mb->pos + length > mb->len)
+      png_error(png_ptr, "pnggetset: read overflow");
+
+   memcpy(buf, mb->data + mb->pos, length);
+   mb->pos += length;
+}
+
+/* Palette sync after gamma correction.
+ *
+ * When info_ptr->palette and png_ptr->palette are separate buffers,
+ * in-place gamma correction of png_ptr->palette must be synced back
+ * to info_ptr->palette so that png_get_PLTE returns the corrected
+ * values.
+ */
+#define PLTE_SYNC_NPALETTE 4
+
+static const png_color plte_sync_original[PLTE_SYNC_NPALETTE] =
+{
+   {  64,  96, 128 },
+   { 128, 160, 192 },
+   { 192, 224, 240 },
+   {  32,  48,  64 }
+};
+
+static int
+test_plte_palette_sync(void)
+{
+   mem_buf buf;
+   png_structp png_ptr;
+   png_infop info_ptr;
+   png_colorp got_palette;
+   int num_palette;
+   double file_gamma;
+   png_byte row[1];
+   int i;
+   int changed;
+
+   /* Write a 1x1 palette PNG with gAMA = 1.0 (linear). */
+   buf.len = 0;
+   buf.pos = 0;
+
+   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+       NULL, NULL, NULL);
+   if (png_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_write_struct failed\n");
+      return 1;
+   }
+
+   info_ptr = png_create_info_struct(png_ptr);
+   if (info_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_info_struct failed\n");
+      png_destroy_write_struct(&png_ptr, NULL);
+      return 1;
+   }
+
+   if (setjmp(png_jmpbuf(png_ptr)))
+   {
+      fprintf(stderr, "pnggetset: libpng error in test_plte_palette_sync"
+          " (write)\n");
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+      return 1;
+   }
+
+   png_set_write_fn(png_ptr, &buf, mem_write, mem_flush);
+   png_set_IHDR(png_ptr, info_ptr, 1, 1, 8, PNG_COLOR_TYPE_PALETTE,
+       PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
+       PNG_FILTER_TYPE_BASE);
+   png_set_PLTE(png_ptr, info_ptr,
+       (png_colorp)plte_sync_original, PLTE_SYNC_NPALETTE);
+   png_set_gAMA(png_ptr, info_ptr, 1.0);
+   png_write_info(png_ptr, info_ptr);
+
+   row[0] = 0;
+   png_write_row(png_ptr, row);
+   png_write_end(png_ptr, info_ptr);
+   png_destroy_write_struct(&png_ptr, &info_ptr);
+
+   /* Read back with gamma correction as the sole transform. */
+   buf.pos = 0;
+
+   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+       NULL, NULL, NULL);
+   if (png_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_read_struct failed\n");
+      return 1;
+   }
+
+   info_ptr = png_create_info_struct(png_ptr);
+   if (info_ptr == NULL)
+   {
+      fprintf(stderr, "pnggetset: png_create_info_struct failed\n");
+      png_destroy_read_struct(&png_ptr, NULL, NULL);
+      return 1;
+   }
+
+   if (setjmp(png_jmpbuf(png_ptr)))
+   {
+      fprintf(stderr, "pnggetset: libpng error in test_plte_palette_sync"
+          " (read)\n");
+      png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+      return 1;
+   }
+
+   png_set_read_fn(png_ptr, &buf, mem_read);
+   png_read_info(png_ptr, info_ptr);
+
+   if (png_get_gAMA(png_ptr, info_ptr, &file_gamma) == 0)
+   {
+      fprintf(stderr, "pnggetset: gAMA chunk not found\n");
+      png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+      return 1;
+   }
+
+   png_set_gamma(png_ptr, 2.2, file_gamma);
+   png_read_update_info(png_ptr, info_ptr);
+
+   if (png_get_PLTE(png_ptr, info_ptr, &got_palette, &num_palette) == 0)
+   {
+      fprintf(stderr, "pnggetset: png_get_PLTE failed after update\n");
+      png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+      return 1;
+   }
+
+   if (num_palette != PLTE_SYNC_NPALETTE)
+   {
+      fprintf(stderr, "pnggetset: palette size %d, expected %d\n",
+          num_palette, PLTE_SYNC_NPALETTE);
+      png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+      return 1;
+   }
+
+   /* Every entry must differ from the original after gamma correction
+    * (file_gamma=1.0, screen_gamma=2.2).  If the sync was skipped,
+    * info_ptr->palette still holds the stale pre-correction values.
+    */
+   changed = 0;
+
+   for (i = 0; i < PLTE_SYNC_NPALETTE; i++)
+   {
+      if ((got_palette[i].red != plte_sync_original[i].red)
+          || (got_palette[i].green != plte_sync_original[i].green)
+          || (got_palette[i].blue != plte_sync_original[i].blue))
+      {
+         changed++;
+      }
+      else
+      {
+         fprintf(stderr,
+             "pnggetset: palette entry %d NOT gamma-corrected: "
+             "got {%u, %u, %u}, same as original\n",
+             i,
+             (unsigned)got_palette[i].red,
+             (unsigned)got_palette[i].green,
+             (unsigned)got_palette[i].blue);
+      }
+   }
+
+   png_read_row(png_ptr, row, NULL);
+   png_read_end(png_ptr, NULL);
+   png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+
+   if (changed != PLTE_SYNC_NPALETTE)
+   {
+      fprintf(stderr,
+          "pnggetset: only %d of %d palette entries were "
+          "gamma-corrected (palette sync failed)\n",
+          changed, PLTE_SYNC_NPALETTE);
+      return 1;
+   }
+
+   return 0;
+}
+
+int
+main(void)
+{
+   int result = 0;
+
+   printf("Testing PLTE get-then-set roundtrip... ");
+   fflush(stdout);
+   if (test_plte_roundtrip() != 0)
+   {
+      printf("FAIL\n");
+      result = 1;
+   }
+   else
+      printf("PASS\n");
+
+#ifdef PNG_hIST_SUPPORTED
+   printf("Testing hIST get-then-set roundtrip... ");
+   fflush(stdout);
+   if (test_hist_roundtrip() != 0)
+   {
+      printf("FAIL\n");
+      result = 1;
+   }
+   else
+      printf("PASS\n");
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+   printf("Testing tRNS get-then-set roundtrip... ");
+   fflush(stdout);
+   if (test_trns_roundtrip() != 0)
+   {
+      printf("FAIL\n");
+      result = 1;
+   }
+   else
+      printf("PASS\n");
+#endif
+
+#ifdef PNG_TEXT_SUPPORTED
+   printf("Testing tEXt get-then-set roundtrip... ");
+   fflush(stdout);
+   if (test_text_roundtrip() != 0)
+   {
+      printf("FAIL\n");
+      result = 1;
+   }
+   else
+      printf("PASS\n");
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+   printf("Testing sPLT get-then-set roundtrip... ");
+   fflush(stdout);
+   if (test_splt_roundtrip() != 0)
+   {
+      printf("FAIL\n");
+      result = 1;
+   }
+   else
+      printf("PASS\n");
+#endif
+
+#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
+   printf("Testing unknown chunks get-then-set roundtrip... ");
+   fflush(stdout);
+   if (test_unknown_roundtrip() != 0)
+   {
+      printf("FAIL\n");
+      result = 1;
+   }
+   else
+      printf("PASS\n");
+#endif
+
+   printf("Testing PLTE sync after gamma correction... ");
+   fflush(stdout);
+   if (test_plte_palette_sync() != 0)
+   {
+      printf("FAIL\n");
+      result = 1;
+   }
+   else
+      printf("PASS\n");
+
+   return result;
+}
diff --git a/contrib/libtests/pngstest.c b/contrib/libtests/pngstest.c
index 8fcd575..171d748 100644
--- a/contrib/libtests/pngstest.c
+++ b/contrib/libtests/pngstest.c
@@ -1,6 +1,6 @@
 /* pngstest.c
  *
- * Copyright (c) 2021-2025 Cosmin Truta
+ * Copyright (c) 2021-2026 Cosmin Truta
  * Copyright (c) 2013-2017 John Cunningham Bowler
  *
  * This code is released under the libpng license.
@@ -323,18 +323,19 @@
 }
 #endif /* unused */
 
-#define USE_FILE 1       /* else memory */
-#define USE_STDIO 2      /* else use file name */
-#define STRICT 4         /* fail on warnings too */
+#define USE_FILE 1           /* else memory */
+#define USE_STDIO 2          /* else use file name */
+#define STRICT 4             /* fail on warnings too */
 #define VERBOSE 8
-#define KEEP_TMPFILES 16 /* else delete temporary files */
+#define KEEP_TMPFILES 16     /* else delete temporary files */
 #define KEEP_GOING 32
 #define ACCUMULATE 64
 #define FAST_WRITE 128
 #define sRGB_16BIT 256
-#define NO_RESEED  512   /* do not reseed on each new file */
-#define GBG_ERROR 1024   /* do not ignore the gamma+background_rgb_to_gray
-                          * libpng warning. */
+#define NO_RESEED 512        /* do not reseed on each new file */
+#define GBG_ERROR 1024       /* do not ignore the gamma+background_rgb_to_gray
+                              * warning. */
+#define NEGATIVE_STRIDE 2048 /* negate row stride for bottom-up layout */
 
 static void
 print_opts(png_uint_32 opts)
@@ -361,6 +362,8 @@
       printf(" --noreseed");
    if (opts & GBG_ERROR)
       printf(" --fault-gbg-warning");
+   if (opts & NEGATIVE_STRIDE)
+      printf(" --negative-stride");
 }
 
 #define FORMAT_NO_CHANGE 0x80000000 /* additional flag */
@@ -2662,7 +2665,7 @@
        * of the loop until the end; this validates the color-mapped data to
        * ensure all pixels are valid color-map indexes.
        */
-      for (y=0, match=1; y<height && match; ++y, ppa += stridea, ppb += strideb)
+      for (y=0, match=1; y<height && match; ++y)
       {
          png_uint_32 x;
 
@@ -2681,6 +2684,19 @@
             if (aval > amax)
                amax = aval;
          }
+
+         /* Increment with care!
+          * With negative strides, an unguarded final increment would produce
+          * a pointer before the allocated object, which is undefined behavior.
+          * Standard C allows one-after-end pointers, not one-before-beginning
+          * pointers, and this restriction stands regardless of whether the
+          * pointers are dereferenced or not.
+          */
+         if (y+1 < height)
+         {
+            ppa += stridea;
+            ppb += strideb;
+         }
       }
 
       /* If the buffers match then the colormaps must too. */
@@ -2790,7 +2806,7 @@
     * If an alpha channel has been *added* then it must have the relevant opaque
     * value (255 or 65535).
     *
-    * The fist two the tests (in the order given above) (using the boolean
+    * The first two tests (in the order given above) (using the boolean
     * equivalence !a && !b == !(a || b))
     */
    if (!(((formata ^ formatb) & PNG_FORMAT_FLAG_LINEAR) |
@@ -2857,7 +2873,7 @@
       btoa[3] = btoa[2] = btoa[1] = btoa[0] = 4; /* 4 == not present */
    }
 
-   for (y=0; y<height; ++y, rowa += stridea, rowb += strideb)
+   for (y=0; y<height; ++y)
    {
       const png_byte *ppa, *ppb;
       png_uint_32 x;
@@ -2939,6 +2955,16 @@
          if (!cmppixel(&tr, psa, psb, x, y) && (a->opts & KEEP_GOING) == 0)
             return 0; /* error case */
       }
+
+      /* Increment with care!
+       * (See the previous comment about preventing negative strides from
+       * causing undefined behavior.)
+       */
+      if (y+1 < height)
+      {
+         rowa += stridea;
+         rowb += strideb;
+      }
    }
 
    /* If requested, copy the error values back from the Transform. */
@@ -3040,6 +3066,9 @@
       image->stride = PNG_IMAGE_ROW_STRIDE(image->image) + image->stride_extra;
       allocbuffer(image);
 
+      if (image->opts & NEGATIVE_STRIDE)
+         image->stride = -image->stride;
+
       result = png_image_finish_read(&image->image, background,
          image->buffer+16, (png_int_32)image->stride, image->colormap);
 
@@ -3577,6 +3606,35 @@
          opts |= NO_RESEED;
       else if (strcmp(arg, "--fault-gbg-warning") == 0)
          opts |= GBG_ERROR;
+      else if (strcmp(arg, "--negative-stride") == 0)
+         opts |= NEGATIVE_STRIDE;
+      else if (strcmp(arg, "--stride-extra") == 0)
+      {
+         if (c+1 < argc)
+         {
+            char *ep;
+            unsigned long val = strtoul(argv[++c], &ep, 0);
+
+            if (ep > argv[c] && *ep == 0 && val <= 65535)
+               stride_extra = (int)val;
+
+            else
+            {
+               fflush(stdout);
+               fprintf(stderr, "%s: bad argument for --stride-extra: %s\n",
+                  argv[0], argv[c]);
+               exit(99);
+            }
+         }
+
+         else
+         {
+            fflush(stdout);
+            fprintf(stderr, "%s: missing argument for --stride-extra\n",
+               argv[0]);
+            exit(99);
+         }
+      }
       else if (strcmp(arg, "--tmpfile") == 0)
       {
          if (c+1 < argc)
diff --git a/contrib/libtests/pngvalid.c b/contrib/libtests/pngvalid.c
index daf558e..70e887c 100644
--- a/contrib/libtests/pngvalid.c
+++ b/contrib/libtests/pngvalid.c
@@ -6179,7 +6179,7 @@
             this->have_tRNS = 0;
 
             /* Check the input, original, channel value here against the
-             * original tRNS gray chunk valie.
+             * original tRNS gray chunk value.
              */
             if (this->red == display->transparent.red)
                this->alphaf = 0;
@@ -7514,7 +7514,7 @@
    double green_coefficient;
    double blue_coefficient;
 
-   /* Set if the coeefficients have been overridden. */
+   /* Set if the coefficients have been overridden. */
    int coefficients_overridden;
 } data;
 
@@ -8988,7 +8988,7 @@
       /* For max 0 or 1 continue until the counter overflows: */
       counter >>= 1;
 
-      /* Continue if any entry hasn't reacked the max. */
+      /* Continue if any entry hasn't reached the max. */
       if (max > 1 && next->local_use < max)
          return 1;
       next = next->list;
@@ -9674,7 +9674,7 @@
        *  od: encoded result from libpng
        */
 
-      /* Now we have the numbers for real errors, both absolute values as as a
+      /* Now we have the numbers for real errors, both absolute values as a
        * percentage of the correct value (output):
        */
       error = fabs(input_sample-output);
@@ -11648,7 +11648,7 @@
 #  endif
 
    /* The following allows results to pass if they correspond to anything in the
-    * transformed range [input-.5,input+.5]; this is is required because of the
+    * transformed range [input-.5,input+.5]; this is required because of the
     * way libpng treats the 16_TO_8 flag when building the gamma tables in
     * releases up to 1.6.0.
     *
diff --git a/contrib/mips-msa/README b/contrib/mips-msa/README
index 10acc93..f64d6ea 100644
--- a/contrib/mips-msa/README
+++ b/contrib/mips-msa/README
@@ -46,7 +46,7 @@
 The file is a fragment of C code. It should not define any 'extern' symbols;
 everything should be static.  It must define the function:
 
-static int png_have_msa(png_structp png_ptr);
+static int png_have_msa(png_struct *png_ptr);
 
 That function must return 1 if MIPS MSA instructions are supported, 0 if not.
 It must not execute png_error unless it detects a bug.  A png_error will prevent
diff --git a/contrib/pngminus/README.txt b/contrib/pngminus/README.txt
index f7f6ecb..eb168c3 100644
--- a/contrib/pngminus/README.txt
+++ b/contrib/pngminus/README.txt
@@ -53,7 +53,7 @@
 It does not handle B&W pbm-files, but instead you could do pgm with bit-
 depth 1.
 
-The downside of this approach is that you can not use them on images
+The downside of this approach is that you cannot use them on images
 that require blocks of memory bigger than 64k (the DOS version). For
 larger images you will get an out-of-memory error.
 
diff --git a/contrib/powerpc-vsx/README b/contrib/powerpc-vsx/README
index 4970a52..1505e8c 100644
--- a/contrib/powerpc-vsx/README
+++ b/contrib/powerpc-vsx/README
@@ -44,7 +44,7 @@
 The file is a fragment of C code. It should not define any 'extern' symbols;
 everything should be static.  It must define the function:
 
-static int png_have_vsx(png_structp png_ptr);
+static int png_have_vsx(png_struct *png_ptr);
 
 That function must return 1 if POWERPC_VSX instructions are supported, 0 if not.
 It must not execute png_error unless it detects a bug.  A png_error will prevent
diff --git a/contrib/tools/pngfix.c b/contrib/tools/pngfix.c
index a0c623a..9c5eebd 100644
--- a/contrib/tools/pngfix.c
+++ b/contrib/tools/pngfix.c
@@ -52,23 +52,10 @@
 #  undef const
 #endif
 
-/* zlib.h has mediocre z_const use before 1.2.6, this stuff is for compatibility
- * with older builds.
- */
-#if ZLIB_VERNUM < 0x1260
-#  define PNGZ_MSG_CAST(s) constcast(char*,s)
-#  define PNGZ_INPUT_CAST(b) constcast(png_byte *,b)
-#else
-#  define PNGZ_MSG_CAST(s) (s)
-#  define PNGZ_INPUT_CAST(b) (b)
-#endif
-
 #ifndef PNG_MAXIMUM_INFLATE_WINDOW
 #  error pngfix requires libpng with PNG_MAXIMUM_INFLATE_WINDOW supported
 #endif
 
-#if ZLIB_VERNUM >= 0x1240
-
 /* Copied from pngpriv.h */
 #ifdef __cplusplus
 #  define voidcast(type, value) static_cast<type>(value)
@@ -2653,7 +2640,7 @@
 
          case ZLIB_OK:
             /* Truncated stream; unrecoverable, gets converted to ZLIB_FATAL */
-            zlib.z.msg = PNGZ_MSG_CAST("[truncated]");
+            zlib.z.msg = "[truncated]";
             zlib_message(&zlib, 0/*expected*/);
             /* FALLTHROUGH */
 
@@ -2692,8 +2679,7 @@
 
                      /* Output the error that wasn't output before: */
                      if (zlib.z.msg == NULL)
-                        zlib.z.msg = PNGZ_MSG_CAST(
-                           "invalid distance too far back");
+                        zlib.z.msg = "invalid distance too far back";
                      zlib_message(&zlib, 0/*stream error*/);
                      zlib_end(&zlib);
                      return 0;
@@ -4009,17 +3995,6 @@
    return global_end(&global);
 }
 
-#else /* ZLIB_VERNUM < 0x1240 */
-int
-main(void)
-{
-   fprintf(stderr,
-      "pngfix needs libpng with a zlib >=1.2.4 (not 0x%x)\n",
-      ZLIB_VERNUM);
-   return 77;
-}
-#endif /* ZLIB_VERNUM */
-
 #else /* No read support */
 
 int
diff --git a/contrib/visupng/VisualPng.c b/contrib/visupng/VisualPng.c
index 6f7b1df..8c93e6b 100644
--- a/contrib/visupng/VisualPng.c
+++ b/contrib/visupng/VisualPng.c
@@ -832,7 +832,7 @@
         cxNewSize = cxWinSize - 2 * MARGIN;
         cyNewSize = cyWinSize - 2 * MARGIN;
 
-        /* stretch the image to it's window determined size */
+        /* stretch the image to its window determined size */
 
         /* the following two are mathematically the same, but the first
          * has side-effects because of rounding
diff --git a/intel/check.h b/intel/check.h
index 47ce709..1081a57 100644
--- a/intel/check.h
+++ b/intel/check.h
@@ -9,7 +9,7 @@
  * For conditions of distribution and use, see the disclaimer
  * and license in png.h
  */
-/* PNG_INTEL_SSE_IMPLEMENTATION is used in the actual implementation to selecct
+/* PNG_INTEL_SSE_IMPLEMENTATION is used in the actual implementation to select
  * the correct code.
  */
 #if defined(__SSE4_1__) || defined(__AVX__)
diff --git a/manuals/libpng-manual.txt b/manuals/libpng-manual.txt
index acf6d6c..258ad25 100644
--- a/manuals/libpng-manual.txt
+++ b/manuals/libpng-manual.txt
@@ -1,6 +1,6 @@
 libpng-manual.txt - A description on how to use and modify libpng
 
- Copyright (c) 2018-2025 Cosmin Truta
+ Copyright (c) 2018-2026 Cosmin Truta
  Copyright (c) 1998-2018 Glenn Randers-Pehrson
 
  This document is released under the libpng license.
@@ -9,9 +9,9 @@
 
  Based on:
 
- libpng version 1.6.36, December 2018, through 1.6.50 - July 2025
+ libpng version 1.6.36, December 2018, through 1.6.58 - April 2026
  Updated and distributed by Cosmin Truta
- Copyright (c) 2018-2025 Cosmin Truta
+ Copyright (c) 2018-2026 Cosmin Truta
 
  libpng versions 0.97, January 1998, through 1.6.35 - July 2018
  Updated and distributed by Glenn Randers-Pehrson
@@ -2312,7 +2312,7 @@
    png_uint_32 height = PNG_PASS_ROWS(image_height, pass_number);
 
 Respectively these tell you the width and height of the sub-image
-corresponding to the numbered pass.  'pass' is in in the range 0 to 6 -
+corresponding to the numbered pass.  'pass' is in the range 0 to 6 -
 this can be confusing because the specification refers to the same passes
 as 1 to 7!  Be careful, you must check both the width and height before
 calling png_read_rows() and not call it for that pass if either is zero.
@@ -2451,7 +2451,7 @@
 
 This function may be safely called when the relevant storage has
 already been freed, or has not yet been allocated, or was allocated
-by the user and not by libpng,  and will in those cases do nothing.
+by the user and not by libpng, and will in those cases do nothing.
 The "seq" parameter is ignored if only one item of the selected data
 type, such as PLTE, is allowed.  If "seq" is not -1, and multiple items
 are allowed for the data type identified in the mask, such as text or
@@ -3286,7 +3286,7 @@
 don't have to wait for the disclaimer to go over the modem before
 they start seeing the image.  Finally, keywords should be full
 words, not abbreviations.  Keywords and text are in the ISO 8859-1
-(Latin-1) character set (a superset of regular ASCII) and can not
+(Latin-1) character set (a superset of regular ASCII) and cannot
 contain NUL characters, and should not contain control or other
 unprintable characters.  To make the comments widely readable, stick
 with basic ASCII, and avoid machine specific character set extensions
@@ -3687,7 +3687,7 @@
 
 This function may be safely called when the relevant storage has
 already been freed, or has not yet been allocated, or was allocated
-by the user  and not by libpng,  and will in those cases do nothing.
+by the user and not by libpng, and will in those cases do nothing.
 The "seq" parameter is ignored if only one item of the selected data
 type, such as PLTE, is allowed.  If "seq" is not -1, and multiple items
 are allowed for the data type identified in the mask, such as text or
diff --git a/manuals/libpng.3 b/manuals/libpng.3
index 0097a78..52e10cc 100644
--- a/manuals/libpng.3
+++ b/manuals/libpng.3
@@ -1,4 +1,4 @@
-.TH LIBPNG 3 "October 29, 2025"
+.TH LIBPNG 3 "April 15, 2026"
 .SH NAME
 libpng \- Portable Network Graphics (PNG) Reference Library 1.8.0.git
 
@@ -507,7 +507,7 @@
 .SH LIBPNG.TXT
 libpng-manual.txt - A description on how to use and modify libpng
 
- Copyright (c) 2018-2025 Cosmin Truta
+ Copyright (c) 2018-2026 Cosmin Truta
  Copyright (c) 1998-2018 Glenn Randers-Pehrson
 
  This document is released under the libpng license.
@@ -516,9 +516,9 @@
 
  Based on:
 
- libpng version 1.6.36, December 2018, through 1.6.50 - July 2025
+ libpng version 1.6.36, December 2018, through 1.6.58 - April 2026
  Updated and distributed by Cosmin Truta
- Copyright (c) 2018-2025 Cosmin Truta
+ Copyright (c) 2018-2026 Cosmin Truta
 
  libpng versions 0.97, January 1998, through 1.6.35 - July 2018
  Updated and distributed by Glenn Randers-Pehrson
@@ -2819,7 +2819,7 @@
    png_uint_32 height = PNG_PASS_ROWS(image_height, pass_number);
 
 Respectively these tell you the width and height of the sub-image
-corresponding to the numbered pass.  'pass' is in in the range 0 to 6 -
+corresponding to the numbered pass.  'pass' is in the range 0 to 6 -
 this can be confusing because the specification refers to the same passes
 as 1 to 7!  Be careful, you must check both the width and height before
 calling png_read_rows() and not call it for that pass if either is zero.
@@ -2958,7 +2958,7 @@
 
 This function may be safely called when the relevant storage has
 already been freed, or has not yet been allocated, or was allocated
-by the user and not by libpng,  and will in those cases do nothing.
+by the user and not by libpng, and will in those cases do nothing.
 The "seq" parameter is ignored if only one item of the selected data
 type, such as PLTE, is allowed.  If "seq" is not \-1, and multiple items
 are allowed for the data type identified in the mask, such as text or
@@ -3793,7 +3793,7 @@
 don't have to wait for the disclaimer to go over the modem before
 they start seeing the image.  Finally, keywords should be full
 words, not abbreviations.  Keywords and text are in the ISO 8859-1
-(Latin-1) character set (a superset of regular ASCII) and can not
+(Latin-1) character set (a superset of regular ASCII) and cannot
 contain NUL characters, and should not contain control or other
 unprintable characters.  To make the comments widely readable, stick
 with basic ASCII, and avoid machine specific character set extensions
@@ -4194,7 +4194,7 @@
 
 This function may be safely called when the relevant storage has
 already been freed, or has not yet been allocated, or was allocated
-by the user  and not by libpng,  and will in those cases do nothing.
+by the user and not by libpng, and will in those cases do nothing.
 The "seq" parameter is ignored if only one item of the selected data
 type, such as PLTE, is allowed.  If "seq" is not \-1, and multiple items
 are allowed for the data type identified in the mask, such as text or
diff --git a/manuals/png.5 b/manuals/png.5
index 4a8d577..803f8f6 100644
--- a/manuals/png.5
+++ b/manuals/png.5
@@ -1,4 +1,4 @@
-.TH PNG 5 "July 14, 2025"
+.TH PNG 5 "April 15, 2026"
 .SH NAME
 png \- Portable Network Graphics (PNG) format
 
diff --git a/png.c b/png.c
index ccfc3b6..aacba1a 100644
--- a/png.c
+++ b/png.c
@@ -1,6 +1,6 @@
 /* png.c - location for general purpose libpng functions
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -795,7 +795,7 @@
 #else
    return "\n"
       "libpng version 1.8.0.git\n"
-      "Copyright (c) 2018-2025 Cosmin Truta\n"
+      "Copyright (c) 2018-2026 Cosmin Truta\n"
       "Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson\n"
       "Copyright (c) 1996-1997 Andreas Dilger\n"
       "Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.\n";
@@ -982,48 +982,48 @@
    {
       default:
       case Z_OK:
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected zlib return code");
+         png_ptr->zstream.msg = "unexpected zlib return code";
          break;
 
       case Z_STREAM_END:
          /* Normal exit */
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected end of LZ stream");
+         png_ptr->zstream.msg = "unexpected end of LZ stream";
          break;
 
       case Z_NEED_DICT:
          /* This means the deflate stream did not have a dictionary; this
           * indicates a bogus PNG.
           */
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("missing LZ dictionary");
+         png_ptr->zstream.msg = "missing LZ dictionary";
          break;
 
       case Z_ERRNO:
          /* gz APIs only: should not happen */
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("zlib IO error");
+         png_ptr->zstream.msg = "zlib IO error";
          break;
 
       case Z_STREAM_ERROR:
          /* internal libpng error */
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("bad parameters to zlib");
+         png_ptr->zstream.msg = "bad parameters to zlib";
          break;
 
       case Z_DATA_ERROR:
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("damaged LZ stream");
+         png_ptr->zstream.msg = "damaged LZ stream";
          break;
 
       case Z_MEM_ERROR:
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("insufficient memory");
+         png_ptr->zstream.msg = "insufficient memory";
          break;
 
       case Z_BUF_ERROR:
          /* End of input or output; not a problem if the caller is doing
           * incremental read or write.
           */
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("truncated");
+         png_ptr->zstream.msg = "truncated";
          break;
 
       case Z_VERSION_ERROR:
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("unsupported zlib version");
+         png_ptr->zstream.msg = "unsupported zlib version";
          break;
 
       case PNG_UNEXPECTED_ZLIB_RETURN:
@@ -1032,7 +1032,7 @@
           * and change pngpriv.h.  Note that this message is "... return",
           * whereas the default/Z_OK one is "... return code".
           */
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("unexpected zlib return");
+         png_ptr->zstream.msg = "unexpected zlib return";
          break;
    }
 }
@@ -1142,7 +1142,7 @@
       return 1;
 
    /* The reference white is simply the sum of the end-point (X,Y,Z) vectors so
-    * the fillowing calculates (X+Y+Z) of the reference white (media white,
+    * the following calculates (X+Y+Z) of the reference white (media white,
     * encoding white) itself:
     */
    d = dblue;
@@ -1187,9 +1187,9 @@
     * (-0.0770) because the PNG spec itself requires the xy values to be
     * unsigned.  whitey is also required to be 5 or more to avoid overflow.
     *
-    * Instead the upper limits have been relaxed to accomodate ACES AP1 where
+    * Instead the upper limits have been relaxed to accommodate ACES AP1 where
     * redz ends up as -600 (-0.006).  ProPhotoRGB was already "in range."
-    * The new limit accomodates the AP0 and AP1 ranges for z but not AP0 redy.
+    * The new limit accommodates the AP0 and AP1 ranges for z but not AP0 redy.
     */
    const png_fixed_point fpLimit = PNG_FP_1+(PNG_FP_1/10);
    if (xy->redx   < 0 || xy->redx > fpLimit) return 1;
@@ -1300,7 +1300,7 @@
     *    red-scale + green-scale + blue-scale = 1/white-y = white-scale
     *
     * So now we have a Cramer's rule solution where the determinants are just
-    * 3x3 - far more tractible.  Unfortunately 3x3 determinants still involve
+    * 3x3 - far more tractable.  Unfortunately 3x3 determinants still involve
     * multiplication of three coefficients so we can't guarantee to avoid
     * overflow in the libpng fixed point representation.  Using Cramer's rule in
     * floating point is probably a good choice here, but it's not an option for
@@ -1669,7 +1669,7 @@
     * into R, G and B channels.
     *
     * Previously it was suggested that an RGB profile on grayscale data could be
-    * handled.  However it it is clear that using an RGB profile in this context
+    * handled.  However it is clear that using an RGB profile in this context
     * must be an error - there is no specification of what it means.  Thus it is
     * almost certainly more correct to ignore the profile.
     */
@@ -2887,7 +2887,7 @@
     *
     *    2.2/(2+51/256) == 1.00035524
     *
-    * I.e. vanishly small (<4E-4) but still detectable in 16-bit linear (+/-
+    * I.e. vanishingly small (<4E-4) but still detectable in 16-bit linear (+/-
     * 23).  Note that the Adobe choice seems to be something intended to give an
     * exact number with 8 binary fractional digits - it is the closest to 2.2
     * that is possible a base 2 .8p representation.
diff --git a/png.h b/png.h
index fa91f23..d01b371 100644
--- a/png.h
+++ b/png.h
@@ -2,7 +2,7 @@
  *
  * libpng version 1.8.0.git
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -14,7 +14,7 @@
  *   libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger
  *   libpng versions 0.97, January 1998, through 1.6.35, July 2018:
  *     Glenn Randers-Pehrson
- *   libpng versions 1.6.36, December 2018, through 1.6.50, July 2025:
+ *   libpng versions 1.6.36, December 2018, through 1.6.58, April 2026:
  *     Cosmin Truta
  *   See also "Contributing Authors", below.
  */
@@ -26,8 +26,8 @@
  * PNG Reference Library License version 2
  * ---------------------------------------
  *
- *  * Copyright (c) 1995-2025 The PNG Reference Library Authors.
- *  * Copyright (c) 2018-2025 Cosmin Truta.
+ *  * Copyright (c) 1995-2026 The PNG Reference Library Authors.
+ *  * Copyright (c) 2018-2026 Cosmin Truta.
  *  * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson.
  *  * Copyright (c) 1996-1997 Andreas Dilger.
  *  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -192,16 +192,16 @@
 #define PNG_LIBPNG_VER_STRING "1.8.0.git"
 #define PNG_HEADER_VERSION_STRING " libpng version " PNG_LIBPNG_VER_STRING "\n"
 
-/* The versions of shared library builds should stay in sync, going forward */
-#define PNG_LIBPNG_VER_SHAREDLIB 18
-#define PNG_LIBPNG_VER_SONUM     PNG_LIBPNG_VER_SHAREDLIB /* [Deprecated] */
-#define PNG_LIBPNG_VER_DLLNUM    PNG_LIBPNG_VER_SHAREDLIB /* [Deprecated] */
-
 /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */
 #define PNG_LIBPNG_VER_MAJOR   1
 #define PNG_LIBPNG_VER_MINOR   8
 #define PNG_LIBPNG_VER_RELEASE 0
 
+/* The versions of shared library builds should stay in sync, going forward */
+#define PNG_LIBPNG_VER_SHAREDLIB 18
+#define PNG_LIBPNG_VER_SONUM     PNG_LIBPNG_VER_SHAREDLIB /* [Deprecated] */
+#define PNG_LIBPNG_VER_DLLNUM    PNG_LIBPNG_VER_SHAREDLIB /* [Deprecated] */
+
 /* This should be zero for a public release, or non-zero for a
  * development version.
  */
@@ -2260,7 +2260,7 @@
 #endif
 
 /* Note while png_set_text() will accept a structure whose text,
- * language, and  translated keywords are NULL pointers, the structure
+ * language, and translated keywords are NULL pointers, the structure
  * returned by png_get_text will always contain regular
  * zero-terminated C strings.  They might be empty strings but
  * they will never be NULL pointers.
@@ -3358,7 +3358,7 @@
  *           For backward compatibility the original options are defined as
  *           the 'new' hardware option.  libpng can be compiled without
  *           hardware support (check PNG_TARGET_SPECIFIC_CODE_SUPPORTED and
- *           the documenation in pngtarget.h).
+ *           the documentation in pngtarget.h).
  *
  * SOFTWARE: sometimes software optimizations actually result in performance
  *           decrease on some architectures or systems, or with some sets of
diff --git a/pngconf.h b/pngconf.h
index c7500f8..7b224c0 100644
--- a/pngconf.h
+++ b/pngconf.h
@@ -2,7 +2,7 @@
  *
  * libpng version 1.8.0.git
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
diff --git a/pnglibconf.h.prebuilt b/pnglibconf.h.prebuilt
index 478a693..a932652 100644
--- a/pnglibconf.h.prebuilt
+++ b/pnglibconf.h.prebuilt
@@ -2,7 +2,7 @@
 
 /* libpng version 1.8.0.git */
 
-/* Copyright (c) 2018-2025 Cosmin Truta */
+/* Copyright (c) 2018-2026 Cosmin Truta */
 /* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */
 
 /* This code is released under the libpng license. */
@@ -40,7 +40,6 @@
 #define PNG_INFO_IMAGE_SUPPORTED
 #define PNG_IO_STATE_SUPPORTED
 #define PNG_MNG_FEATURES_SUPPORTED
-#define PNG_POINTER_INDEXING_SUPPORTED
 #define PNG_PROGRESSIVE_READ_SUPPORTED
 #define PNG_READ_16BIT_SUPPORTED
 #define PNG_READ_ALPHA_MODE_SUPPORTED
diff --git a/pngpread.c b/pngpread.c
index f79ba4c..978559a 100644
--- a/pngpread.c
+++ b/pngpread.c
@@ -1,6 +1,6 @@
 /* pngpread.c - read a png file in push mode
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -216,19 +216,12 @@
             return;
          }
 
+         png_crc_finish(png_ptr, png_ptr->push_length);
          png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER;
          return;
       }
       else if (chunk_name == png_fdAT)
       {
-         if (png_ptr->buffer_size < 4)
-         {
-            png_push_save_buffer(png_ptr);
-            return;
-         }
-
-         png_ensure_sequence_number(png_ptr, 4);
-
          if (!(png_ptr->mode & PNG_HAVE_fcTL))
          {
             /* Discard trailing fdATs for frames other than the first. */
@@ -241,6 +234,8 @@
                return;
             }
 
+            png_ensure_sequence_number(png_ptr, png_ptr->push_length);
+            png_crc_finish(png_ptr, png_ptr->push_length - 4);
             png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER;
             return;
          }
@@ -248,6 +243,13 @@
          else
          {
             /* Frame data follows. */
+            if (png_ptr->buffer_size < 4)
+            {
+               png_push_save_buffer(png_ptr);
+               return;
+            }
+
+            png_ensure_sequence_number(png_ptr, png_ptr->push_length);
             png_ptr->idat_size = png_ptr->push_length - 4;
             png_ptr->mode |= PNG_HAVE_IDAT;
             png_ptr->process_mode = PNG_READ_IDAT_MODE;
@@ -291,6 +293,7 @@
             return;
          }
          png_warning(png_ptr, "Ignoring unexpected chunk in APNG sequence");
+         png_crc_finish(png_ptr, png_ptr->push_length);
          png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER;
          return;
       }
@@ -601,7 +604,7 @@
 #ifdef PNG_READ_APNG_SUPPORTED
       if (png_ptr->num_frames_read > 0)
       {
-         png_ensure_sequence_number(png_ptr, 4);
+         png_ensure_sequence_number(png_ptr, png_ptr->push_length);
          png_ptr->idat_size -= 4;
       }
 #endif
@@ -724,7 +727,7 @@
        * change the current behavior (see comments in inflate.c
        * for why this doesn't happen at present with zlib 1.2.5).
        */
-      ret = PNG_INFLATE(png_ptr, Z_SYNC_FLUSH);
+      ret = png_zlib_inflate(png_ptr, Z_SYNC_FLUSH);
 
       /* Check for any failure before proceeding. */
       if (ret != Z_OK && ret != Z_STREAM_END)
diff --git a/pngpriv.h b/pngpriv.h
index 6c407bc..81471f1 100644
--- a/pngpriv.h
+++ b/pngpriv.h
@@ -743,7 +743,7 @@
  *
  * At present these index values are not exported (not part of the public API)
  * so can be changed at will.  For convenience the names are in lexical sort
- * order but with the critical chunks at the start in the order of occurence in
+ * order but with the critical chunks at the start in the order of occurrence in
  * a PNG.
  *
  * PNG_INFO_ values do not exist for every one of these chunk handles; for
@@ -1377,14 +1377,9 @@
    (png_struct *png_ptr),
    PNG_EMPTY);
 
-#if ZLIB_VERNUM >= 0x1240
 PNG_INTERNAL_FUNCTION(int, png_zlib_inflate,
    (png_struct *png_ptr, int flush),
    PNG_EMPTY);
-#  define PNG_INFLATE(pp, flush) png_zlib_inflate(pp, flush)
-#else /* Zlib < 1.2.4 */
-#  define PNG_INFLATE(pp, flush) inflate(&(pp)->zstream, flush)
-#endif /* Zlib < 1.2.4 */
 
 #ifdef PNG_READ_TRANSFORMS_SUPPORTED
 /* Optional call to update the users info structure */
@@ -1803,7 +1798,7 @@
  * not valid it will be the index of a character in the supposed number.
  *
  * The format of a number is defined in the PNG extensions specification
- * and this API is strictly conformant to that spec, not anyone elses!
+ * and this API is strictly conformant to that spec, not anyone else's!
  *
  * The format as a regular expression is:
  *
diff --git a/pngread.c b/pngread.c
index 118fefa..f76e44c 100644
--- a/pngread.c
+++ b/pngread.c
@@ -1,6 +1,6 @@
 /* pngread.c - read a PNG file
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -779,7 +779,7 @@
       png_read_finish_IDAT(png_ptr);
 
 #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
-   /* Report invalid palette index; added at libng-1.5.10 */
+   /* Report invalid palette index; added at libpng-1.5.10 */
    if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE &&
        png_ptr->num_palette_max >= png_ptr->num_palette)
       png_benign_error(png_ptr, "Read palette index exceeding num_palette");
@@ -867,21 +867,19 @@
    png_ptr->quantize_index = NULL;
 #endif
 
-   if ((png_ptr->free_me & PNG_FREE_PLTE) != 0)
-   {
-      png_zfree(png_ptr, png_ptr->palette);
-      png_ptr->palette = NULL;
-   }
-   png_ptr->free_me &= ~PNG_FREE_PLTE;
+   /* png_ptr->palette is always independently allocated (not aliased
+    * with info_ptr->palette), so free it unconditionally.
+    */
+   png_free(png_ptr, png_ptr->palette);
+   png_ptr->palette = NULL;
 
 #if defined(PNG_tRNS_SUPPORTED) || \
     defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED)
-   if ((png_ptr->free_me & PNG_FREE_TRNS) != 0)
-   {
-      png_free(png_ptr, png_ptr->trans_alpha);
-      png_ptr->trans_alpha = NULL;
-   }
-   png_ptr->free_me &= ~PNG_FREE_TRNS;
+   /* png_ptr->trans_alpha is always independently allocated (not aliased
+    * with info_ptr->trans_alpha), so free it unconditionally.
+    */
+   png_free(png_ptr, png_ptr->trans_alpha);
+   png_ptr->trans_alpha = NULL;
 #endif
 
    inflateEnd(&png_ptr->zstream);
@@ -1192,16 +1190,17 @@
 
 typedef struct
 {
-   /* Arguments: */
+   /* Arguments */
    png_image *image;
    void *buffer;
    png_int_32 row_stride;
    void *colormap;
    const png_color *background;
-   /* Local variables: */
+
+   /* Instance variables */
    void *local_row;
    void *first_row;
-   ptrdiff_t row_bytes;             /* step between rows */
+   ptrdiff_t row_step;              /* step between rows */
    int file_encoding;               /* E_ values above */
    png_fixed_point gamma_to_linear; /* For P_FILE, reciprocal of gamma */
    int colormap_processing;         /* PNG_CMAP_ values above */
@@ -1342,7 +1341,7 @@
     * png_struct::chromaticities always exists since the simplified API
     * requires rgb-to-gray.  The mDCV, cICP and cHRM chunks may all set it to
     * a non-sRGB value, so it needs to be checked but **only** if one of
-    * those chunks occured in the file.
+    * those chunks occurred in the file.
     */
    /* Highest priority: check to be safe. */
    if (png_has_chunk(png_ptr, cICP) || png_has_chunk(png_ptr, mDCV))
@@ -2682,7 +2681,7 @@
                   {
                      r = back_r;
                      g = back_g;
-                     b = back_g;
+                     b = back_b;
                   }
 
                   /* Compare the newly-created color-map entry with the one the
@@ -2929,7 +2928,7 @@
       png_uint_32 width = image->width;
       int proc = display->colormap_processing;
       png_byte *first_row = png_voidcast(png_byte *, display->first_row);
-      ptrdiff_t step_row = display->row_bytes;
+      ptrdiff_t row_step = display->row_step;
       int pass;
 
       for (pass = 0; pass < passes; ++pass)
@@ -2959,10 +2958,10 @@
          for (; y<height; y += stepy)
          {
             png_byte *inrow = png_voidcast(png_byte *, display->local_row);
-            png_byte *outrow = first_row + y * step_row;
+            png_byte *outrow = first_row + y * row_step;
             png_byte *row_end = outrow + width;
 
-            /* Read read the libpng data into the temporary buffer. */
+            /* Read the libpng data into the temporary buffer. */
             png_read_row(png_ptr, inrow, NULL);
 
             /* Now process the row according to the processing option, note
@@ -3064,10 +3063,10 @@
                          */
                         if (inrow[0] & 0x80) back_i += 9; /* red */
                         if (inrow[0] & 0x40) back_i += 9;
-                        if (inrow[0] & 0x80) back_i += 3; /* green */
-                        if (inrow[0] & 0x40) back_i += 3;
-                        if (inrow[0] & 0x80) back_i += 1; /* blue */
-                        if (inrow[0] & 0x40) back_i += 1;
+                        if (inrow[1] & 0x80) back_i += 3; /* green */
+                        if (inrow[1] & 0x40) back_i += 3;
+                        if (inrow[2] & 0x80) back_i += 1; /* blue */
+                        if (inrow[2] & 0x40) back_i += 1;
 
                         *outrow = (png_byte)back_i;
                      }
@@ -3168,20 +3167,20 @@
     */
    {
       void *first_row = display->buffer;
-      ptrdiff_t row_bytes = display->row_stride;
+      ptrdiff_t row_step = display->row_stride;
 
-      /* The following expression is designed to work correctly whether it gives
-       * a signed or an unsigned result.
+      /* The following adjustment is to ensure that calculations are correct,
+       * regardless whether row_step is positive or negative.
        */
-      if (row_bytes < 0)
+      if (row_step < 0)
       {
          char *ptr = png_voidcast(char*, first_row);
-         ptr += (image->height-1) * (-row_bytes);
+         ptr += (image->height-1) * (-row_step);
          first_row = png_voidcast(void *, ptr);
       }
 
       display->first_row = first_row;
-      display->row_bytes = row_bytes;
+      display->row_step = row_step;
    }
 
    if (passes == 0)
@@ -3199,7 +3198,7 @@
 
    else
    {
-      png_alloc_size_t row_bytes = (png_alloc_size_t)display->row_bytes;
+      ptrdiff_t row_step = display->row_step;
 
       while (--passes >= 0)
       {
@@ -3209,7 +3208,7 @@
          for (; y > 0; --y)
          {
             png_read_row(png_ptr, row, NULL);
-            row += row_bytes;
+            row += row_step;
          }
       }
 
@@ -3217,6 +3216,61 @@
    }
 }
 
+/* Row reading for interlaced 16-to-8 bit depth conversion with local buffer. */
+static int
+png_image_read_direct_scaled(void *argument)
+{
+   png_image_read_control *display = png_voidcast(png_image_read_control*,
+       argument);
+   png_image *image = display->image;
+   png_struct *png_ptr = image->opaque->png_ptr;
+   png_info *info_ptr = image->opaque->info_ptr;
+   png_byte *local_row = png_voidcast(png_byte *, display->local_row);
+   png_byte *first_row = png_voidcast(png_byte *, display->first_row);
+   ptrdiff_t row_step = display->row_step;
+   size_t row_bytes = png_get_rowbytes(png_ptr, info_ptr);
+   int passes;
+
+   /* Handle interlacing. */
+   switch (png_ptr->interlaced)
+   {
+      case PNG_INTERLACE_NONE:
+         passes = 1;
+         break;
+
+      case PNG_INTERLACE_ADAM7:
+         passes = PNG_INTERLACE_ADAM7_PASSES;
+         break;
+
+      default:
+         png_error(png_ptr, "unknown interlace type");
+   }
+
+   /* Read each pass using local_row as intermediate buffer. */
+   while (--passes >= 0)
+   {
+      png_uint_32 y = image->height;
+      png_byte *output_row = first_row;
+
+      for (; y > 0; --y)
+      {
+         /* Read into local_row (gets transformed 8-bit data). */
+         png_read_row(png_ptr, local_row, NULL);
+
+         /* Copy from local_row to user buffer.
+          * Use row_bytes (i.e. the actual size in bytes of the row data) for
+          * copying into output_row. Use row_step for advancing output_row,
+          * to respect the caller's stride for padding or negative (bottom-up)
+          * layouts.
+          */
+         memcpy(output_row, local_row, row_bytes);
+         output_row += row_step;
+      }
+   }
+
+   return 1;
+}
+
 /* Just the row reading part of png_image_read. */
 static int
 png_image_read_composite(void *argument)
@@ -3244,9 +3298,10 @@
    {
       png_uint_32 height = image->height;
       png_uint_32 width = image->width;
-      ptrdiff_t step_row = display->row_bytes;
+      ptrdiff_t row_step = display->row_step;
       unsigned int channels =
           (image->format & PNG_FORMAT_FLAG_COLOR) != 0 ? 3 : 1;
+      int optimize_alpha = (png_ptr->flags & PNG_FLAG_OPTIMIZE_ALPHA) != 0;
       int pass;
 
       for (pass = 0; pass < passes; ++pass)
@@ -3284,7 +3339,7 @@
             png_read_row(png_ptr, inrow, NULL);
 
             outrow = png_voidcast(png_byte *, display->first_row);
-            outrow += y * step_row;
+            outrow += y * row_step;
             row_end = outrow + width * channels;
 
             /* Now do the composition on each pixel in this row. */
@@ -3303,20 +3358,44 @@
 
                      if (alpha < 255) /* else just use component */
                      {
-                        /* This is PNG_OPTIMIZED_ALPHA, the component value
-                         * is a linear 8-bit value.  Combine this with the
-                         * current outrow[c] value which is sRGB encoded.
-                         * Arithmetic here is 16-bits to preserve the output
-                         * values correctly.
-                         */
-                        component *= 257*255; /* =65535 */
-                        component += (255-alpha)*png_sRGB_table[outrow[c]];
+                        if (optimize_alpha != 0)
+                        {
+                           /* This is PNG_OPTIMIZED_ALPHA, the component value
+                            * is a linear 8-bit value.  Combine this with the
+                            * current outrow[c] value which is sRGB encoded.
+                            * Arithmetic here is 16-bits to preserve the output
+                            * values correctly.
+                            */
+                           component *= 257*255; /* =65535 */
+                           component += (255-alpha)*png_sRGB_table[outrow[c]];
 
-                        /* So 'component' is scaled by 255*65535 and is
-                         * therefore appropriate for the sRGB to linear
-                         * conversion table.
-                         */
-                        component = PNG_sRGB_FROM_LINEAR(component);
+                           /* Clamp to the valid range to defend against
+                            * unforeseen cases where the data might be sRGB
+                            * instead of linear premultiplied.
+                            * (Belt-and-suspenders for CVE-2025-66293.)
+                            */
+                           if (component > 255*65535)
+                              component = 255*65535;
+
+                           /* So 'component' is scaled by 255*65535 and is
+                            * therefore appropriate for the sRGB-to-linear
+                            * conversion table.
+                            */
+                           component = PNG_sRGB_FROM_LINEAR(component);
+                        }
+                        else
+                        {
+                           /* Compositing was already done on the palette
+                            * entries.  The data is sRGB premultiplied on black.
+                            * Composite with the background in sRGB space.
+                            * This is not gamma-correct, but matches what was
+                            * done to the palette.
+                            */
+                           png_uint_32 background = outrow[c];
+                           component += ((255-alpha) * background + 127) / 255;
+                           if (component > 255)
+                              component = 255;
+                        }
                      }
 
                      outrow[c] = (png_byte)component;
@@ -3405,7 +3484,7 @@
           */
          {
             png_byte *first_row = png_voidcast(png_byte *, display->first_row);
-            ptrdiff_t step_row = display->row_bytes;
+            ptrdiff_t row_step = display->row_step;
 
             for (pass = 0; pass < passes; ++pass)
             {
@@ -3437,7 +3516,7 @@
                   {
                      png_byte *inrow = png_voidcast(png_byte *,
                          display->local_row);
-                     png_byte *outrow = first_row + y * step_row;
+                     png_byte *outrow = first_row + y * row_step;
                      png_byte *row_end = outrow + width;
 
                      /* Read the row, which is packed: */
@@ -3482,7 +3561,7 @@
                   {
                      png_byte *inrow = png_voidcast(png_byte *,
                          display->local_row);
-                     png_byte *outrow = first_row + y * step_row;
+                     png_byte *outrow = first_row + y * row_step;
                      png_byte *row_end = outrow + width;
 
                      /* Read the row, which is packed: */
@@ -3528,9 +3607,9 @@
             png_uint_16 *first_row = png_voidcast(png_uint_16 *,
                 display->first_row);
             /* The division by two is safe because the caller passed in a
-             * stride which was multiplied by 2 (below) to get row_bytes.
+             * stride which was multiplied by 2 (below) to get row_step.
              */
-            ptrdiff_t step_row = display->row_bytes / 2;
+            ptrdiff_t row_step = display->row_step / 2;
             unsigned int preserve_alpha = (image->format &
                 PNG_FORMAT_FLAG_ALPHA) != 0;
             unsigned int outchannels = 1U+preserve_alpha;
@@ -3572,7 +3651,7 @@
                for (; y<height; y += stepy)
                {
                   const png_uint_16 *inrow;
-                  png_uint_16 *outrow = first_row + y*step_row;
+                  png_uint_16 *outrow = first_row + y * row_step;
                   png_uint_16 *row_end = outrow + width * outchannels;
 
                   /* Read the row, which is packed: */
@@ -3635,6 +3714,7 @@
    int linear = (format & PNG_FORMAT_FLAG_LINEAR) != 0;
    int do_local_compose = 0;
    int do_local_background = 0; /* to avoid double gamma correction bug */
+   int do_local_scale = 0; /* for interlaced 16-to-8 bit conversion */
    int passes = 0;
 
    /* Add transforms to ensure the correct output format is produced then check
@@ -3768,8 +3848,16 @@
             png_set_expand_16(png_ptr);
 
          else /* 8-bit output */
+         {
             png_set_scale_16(png_ptr);
 
+            /* For interlaced images, use local_row buffer to avoid overflow
+             * in png_combine_row() which writes using IHDR bit-depth.
+             */
+            if (png_ptr->interlaced != 0)
+               do_local_scale = 1;
+         }
+
          change &= ~PNG_FORMAT_FLAG_LINEAR;
       }
 
@@ -4000,23 +4088,23 @@
     */
    {
       void *first_row = display->buffer;
-      ptrdiff_t row_bytes = display->row_stride;
+      ptrdiff_t row_step = display->row_stride;
 
       if (linear != 0)
-         row_bytes *= 2;
+         row_step *= 2;
 
-      /* The following expression is designed to work correctly whether it gives
-       * a signed or an unsigned result.
+      /* The following adjustment is to ensure that calculations are correct,
+       * regardless whether row_step is positive or negative.
        */
-      if (row_bytes < 0)
+      if (row_step < 0)
       {
          char *ptr = png_voidcast(char*, first_row);
-         ptr += (image->height-1) * (-row_bytes);
+         ptr += (image->height - 1) * (-row_step);
          first_row = png_voidcast(void *, ptr);
       }
 
       display->first_row = first_row;
-      display->row_bytes = row_bytes;
+      display->row_step = row_step;
    }
 
    if (do_local_compose != 0)
@@ -4045,9 +4133,27 @@
       return result;
    }
 
+   else if (do_local_scale != 0)
+   {
+      /* For interlaced 16-to-8 conversion, use an intermediate row buffer
+       * to avoid buffer overflows in png_combine_row. The local_row is sized
+       * for the transformed (8-bit) output, preventing the overflow that would
+       * occur if png_combine_row wrote 16-bit data directly to the user buffer.
+       */
+      int result;
+      void *row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
+
+      display->local_row = row;
+      result = png_safe_execute(image, png_image_read_direct_scaled, display);
+      display->local_row = NULL;
+      png_free(png_ptr, row);
+
+      return result;
+   }
+
    else
    {
-      png_alloc_size_t row_bytes = (png_alloc_size_t)display->row_bytes;
+      ptrdiff_t row_step = display->row_step;
 
       while (--passes >= 0)
       {
@@ -4057,7 +4163,7 @@
          for (; y > 0; --y)
          {
             png_read_row(png_ptr, row, NULL);
-            row += row_bytes;
+            row += row_step;
          }
       }
 
@@ -4092,7 +4198,7 @@
             row_stride = (png_int_32)/*SAFE*/png_row_stride;
 
          if (row_stride < 0)
-            check = (png_uint_32)(-row_stride);
+            check = -(png_uint_32)row_stride;
 
          else
             check = (png_uint_32)row_stride;
diff --git a/pngrtran.c b/pngrtran.c
index e42c99d..38a9fb9 100644
--- a/pngrtran.c
+++ b/pngrtran.c
@@ -1,6 +1,6 @@
 /* pngrtran.c - transforms the data in a row for PNG readers
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -213,7 +213,7 @@
  *
  * Terminology (assuming power law, "gamma", encodings):
  *    "screen" gamma: a power law imposed by the output device when digital
- *    samples are converted to visible light output.  The EOTF - volage to
+ *    samples are converted to visible light output.  The EOTF - voltage to
  *    luminance on output.
  *
  *    "file" gamma: a power law used to encode luminance levels from the input
@@ -476,15 +476,29 @@
    if (png_rtran_ok(png_ptr, 0) == 0)
       return;
 
+   if (palette == NULL)
+      return;
+
    png_ptr->transformations |= PNG_QUANTIZE;
 
    if (full_quantize == 0)
    {
       int i;
 
+      /* Initialize the array to index colors.
+       *
+       * Ensure quantize_index can fit 256 elements (PNG_MAX_PALETTE_LENGTH)
+       * rather than num_palette elements. This is to prevent buffer overflows
+       * caused by malformed PNG files with out-of-range palette indices.
+       *
+       * Be careful to avoid leaking memory. Applications are allowed to call
+       * this function more than once per png_struct.
+       */
+      png_free(png_ptr, png_ptr->quantize_index);
+      png_ptr->quantize_index = NULL;
       png_ptr->quantize_index = (png_byte *)png_malloc(png_ptr,
-          (png_alloc_size_t)num_palette);
-      for (i = 0; i < num_palette; i++)
+          PNG_MAX_PALETTE_LENGTH);
+      for (i = 0; i < PNG_MAX_PALETTE_LENGTH; i++)
          png_ptr->quantize_index[i] = (png_byte)i;
    }
 
@@ -496,15 +510,14 @@
           * Perhaps not the best solution, but good enough.
           */
 
-         int i;
+         png_byte *quantize_sort;
+         int i, j;
 
-         /* Initialize an array to sort colors */
-         png_ptr->quantize_sort = (png_byte *)png_malloc(png_ptr,
+         /* Initialize the local array to sort colors. */
+         quantize_sort = (png_byte *)png_malloc(png_ptr,
              (png_alloc_size_t)num_palette);
-
-         /* Initialize the quantize_sort array */
          for (i = 0; i < num_palette; i++)
-            png_ptr->quantize_sort[i] = (png_byte)i;
+            quantize_sort[i] = (png_byte)i;
 
          /* Find the least used palette entries by starting a
           * bubble sort, and running it until we have sorted
@@ -516,19 +529,18 @@
          for (i = num_palette - 1; i >= maximum_colors; i--)
          {
             int done; /* To stop early if the list is pre-sorted */
-            int j;
 
             done = 1;
             for (j = 0; j < i; j++)
             {
-               if (histogram[png_ptr->quantize_sort[j]]
-                   < histogram[png_ptr->quantize_sort[j + 1]])
+               if (histogram[quantize_sort[j]]
+                   < histogram[quantize_sort[j + 1]])
                {
                   png_byte t;
 
-                  t = png_ptr->quantize_sort[j];
-                  png_ptr->quantize_sort[j] = png_ptr->quantize_sort[j + 1];
-                  png_ptr->quantize_sort[j + 1] = t;
+                  t = quantize_sort[j];
+                  quantize_sort[j] = quantize_sort[j + 1];
+                  quantize_sort[j + 1] = t;
                   done = 0;
                }
             }
@@ -540,18 +552,18 @@
          /* Swap the palette around, and set up a table, if necessary */
          if (full_quantize != 0)
          {
-            int j = num_palette;
+            j = num_palette;
 
             /* Put all the useful colors within the max, but don't
              * move the others.
              */
             for (i = 0; i < maximum_colors; i++)
             {
-               if ((int)png_ptr->quantize_sort[i] >= maximum_colors)
+               if ((int)quantize_sort[i] >= maximum_colors)
                {
                   do
                      j--;
-                  while ((int)png_ptr->quantize_sort[j] >= maximum_colors);
+                  while ((int)quantize_sort[j] >= maximum_colors);
 
                   palette[i] = palette[j];
                }
@@ -559,7 +571,7 @@
          }
          else
          {
-            int j = num_palette;
+            j = num_palette;
 
             /* Move all the used colors inside the max limit, and
              * develop a translation table.
@@ -567,13 +579,13 @@
             for (i = 0; i < maximum_colors; i++)
             {
                /* Only move the colors we need to */
-               if ((int)png_ptr->quantize_sort[i] >= maximum_colors)
+               if ((int)quantize_sort[i] >= maximum_colors)
                {
                   png_color tmp_color;
 
                   do
                      j--;
-                  while ((int)png_ptr->quantize_sort[j] >= maximum_colors);
+                  while ((int)quantize_sort[j] >= maximum_colors);
 
                   tmp_color = palette[j];
                   palette[j] = palette[i];
@@ -611,8 +623,7 @@
                }
             }
          }
-         png_free(png_ptr, png_ptr->quantize_sort);
-         png_ptr->quantize_sort = NULL;
+         png_free(png_ptr, quantize_sort);
       }
       else
       {
@@ -682,8 +693,8 @@
                          break;
 
                      t->next = hash[d];
-                     t->left = (png_byte)i;
-                     t->right = (png_byte)j;
+                     t->left = png_ptr->palette_to_index[i];
+                     t->right = png_ptr->palette_to_index[j];
                      hash[d] = t;
                   }
                }
@@ -785,7 +796,13 @@
    }
    if (png_ptr->palette == NULL)
    {
-      png_ptr->palette = palette;
+      /* Allocate an owned copy rather than aliasing the caller's pointer,
+       * so that png_read_destroy can free png_ptr->palette unconditionally.
+       */
+      png_ptr->palette = png_voidcast(png_colorp, png_calloc(png_ptr,
+          PNG_MAX_PALETTE_LENGTH * (sizeof (png_color))));
+      memcpy(png_ptr->palette, palette, (unsigned int)num_palette *
+          (sizeof (png_color)));
    }
    png_ptr->num_palette = (png_uint_16)num_palette;
 
@@ -1338,7 +1355,7 @@
    if (file_gamma != 0)
       return file_gamma;
 
-   /* If png_reciprocal oveflows it returns 0 which indicates to the caller that
+   /* If png_reciprocal overflows, it returns 0, indicating to the caller that
     * there is no usable file gamma.  (The checks added to png_set_gamma and
     * png_set_alpha_mode should prevent a screen_gamma which would overflow.)
     */
@@ -1756,19 +1773,51 @@
                   }
                   else /* if (png_ptr->trans_alpha[i] != 0xff) */
                   {
-                     png_byte v, w;
+                     if ((png_ptr->flags & PNG_FLAG_OPTIMIZE_ALPHA) != 0)
+                     {
+                        /* Premultiply only:
+                         * component = round((component * alpha) / 255)
+                         */
+                        png_uint_32 component;
 
-                     v = png_ptr->gamma_to_1[palette[i].red];
-                     png_composite(w, v, png_ptr->trans_alpha[i], back_1.red);
-                     palette[i].red = png_ptr->gamma_from_1[w];
+                        component = png_ptr->gamma_to_1[palette[i].red];
+                        component =
+                            (component * png_ptr->trans_alpha[i] + 128) / 255;
+                        palette[i].red = png_ptr->gamma_from_1[component];
 
-                     v = png_ptr->gamma_to_1[palette[i].green];
-                     png_composite(w, v, png_ptr->trans_alpha[i], back_1.green);
-                     palette[i].green = png_ptr->gamma_from_1[w];
+                        component = png_ptr->gamma_to_1[palette[i].green];
+                        component =
+                            (component * png_ptr->trans_alpha[i] + 128) / 255;
+                        palette[i].green = png_ptr->gamma_from_1[component];
 
-                     v = png_ptr->gamma_to_1[palette[i].blue];
-                     png_composite(w, v, png_ptr->trans_alpha[i], back_1.blue);
-                     palette[i].blue = png_ptr->gamma_from_1[w];
+                        component = png_ptr->gamma_to_1[palette[i].blue];
+                        component =
+                            (component * png_ptr->trans_alpha[i] + 128) / 255;
+                        palette[i].blue = png_ptr->gamma_from_1[component];
+                     }
+                     else
+                     {
+                        /* Composite with background color:
+                         * component =
+                         *    alpha * component + (1 - alpha) * background
+                         */
+                        png_byte v, w;
+
+                        v = png_ptr->gamma_to_1[palette[i].red];
+                        png_composite(w, v,
+                            png_ptr->trans_alpha[i], back_1.red);
+                        palette[i].red = png_ptr->gamma_from_1[w];
+
+                        v = png_ptr->gamma_to_1[palette[i].green];
+                        png_composite(w, v,
+                            png_ptr->trans_alpha[i], back_1.green);
+                        palette[i].green = png_ptr->gamma_from_1[w];
+
+                        v = png_ptr->gamma_to_1[palette[i].blue];
+                        png_composite(w, v,
+                            png_ptr->trans_alpha[i], back_1.blue);
+                        palette[i].blue = png_ptr->gamma_from_1[w];
+                     }
                   }
                }
                else
@@ -1786,6 +1835,7 @@
              * transformations elsewhere.
              */
             png_ptr->transformations &= ~(PNG_COMPOSE | PNG_GAMMA);
+            png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA;
          } /* color_type == PNG_COLOR_TYPE_PALETTE */
 
          /* if (png_ptr->background_gamma_type!=PNG_BACKGROUND_GAMMA_UNKNOWN) */
@@ -2003,6 +2053,17 @@
 {
    png_debug(1, "in png_read_transform_info");
 
+   if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE &&
+       info_ptr->palette != NULL && png_ptr->palette != NULL)
+   {
+      /* Sync info_ptr->palette with png_ptr->palette, which may
+       * have been modified by png_init_read_transformations
+       * (e.g. for gamma correction or background compositing).
+       */
+      memcpy(info_ptr->palette, png_ptr->palette,
+          PNG_MAX_PALETTE_LENGTH * (sizeof (png_color)));
+   }
+
 #ifdef PNG_READ_EXPAND_SUPPORTED
    if ((png_ptr->transformations & PNG_EXPAND) != 0)
    {
@@ -2297,7 +2358,7 @@
       }
       row_info->bit_depth = 8;
       row_info->pixel_depth = (png_byte)(8 * row_info->channels);
-      row_info->rowbytes = row_width * row_info->channels;
+      row_info->rowbytes = (size_t)row_width * row_info->channels;
    }
 }
 #endif
@@ -2499,7 +2560,7 @@
 
       row_info->bit_depth = 8;
       row_info->pixel_depth = (png_byte)(8 * row_info->channels);
-      row_info->rowbytes = row_info->width * row_info->channels;
+      row_info->rowbytes = (size_t)row_info->width * row_info->channels;
    }
 }
 #endif
@@ -2527,7 +2588,7 @@
 
       row_info->bit_depth = 8;
       row_info->pixel_depth = (png_byte)(8 * row_info->channels);
-      row_info->rowbytes = row_info->width * row_info->channels;
+      row_info->rowbytes = (size_t)row_info->width * row_info->channels;
    }
 }
 #endif
@@ -2763,7 +2824,7 @@
             *(--dp) = lo_filler;
             row_info->channels = 2;
             row_info->pixel_depth = 16;
-            row_info->rowbytes = row_width * 2;
+            row_info->rowbytes = (size_t)row_width * 2;
          }
 
          else
@@ -2778,7 +2839,7 @@
             }
             row_info->channels = 2;
             row_info->pixel_depth = 16;
-            row_info->rowbytes = row_width * 2;
+            row_info->rowbytes = (size_t)row_width * 2;
          }
       }
 
@@ -2801,7 +2862,7 @@
             *(--dp) = hi_filler;
             row_info->channels = 2;
             row_info->pixel_depth = 32;
-            row_info->rowbytes = row_width * 4;
+            row_info->rowbytes = (size_t)row_width * 4;
          }
 
          else
@@ -2818,7 +2879,7 @@
             }
             row_info->channels = 2;
             row_info->pixel_depth = 32;
-            row_info->rowbytes = row_width * 4;
+            row_info->rowbytes = (size_t)row_width * 4;
          }
       }
 #endif
@@ -2842,7 +2903,7 @@
             *(--dp) = lo_filler;
             row_info->channels = 4;
             row_info->pixel_depth = 32;
-            row_info->rowbytes = row_width * 4;
+            row_info->rowbytes = (size_t)row_width * 4;
          }
 
          else
@@ -2859,7 +2920,7 @@
             }
             row_info->channels = 4;
             row_info->pixel_depth = 32;
-            row_info->rowbytes = row_width * 4;
+            row_info->rowbytes = (size_t)row_width * 4;
          }
       }
 
@@ -2886,7 +2947,7 @@
             *(--dp) = hi_filler;
             row_info->channels = 4;
             row_info->pixel_depth = 64;
-            row_info->rowbytes = row_width * 8;
+            row_info->rowbytes = (size_t)row_width * 8;
          }
 
          else
@@ -2908,7 +2969,7 @@
 
             row_info->channels = 4;
             row_info->pixel_depth = 64;
-            row_info->rowbytes = row_width * 8;
+            row_info->rowbytes = (size_t)row_width * 8;
          }
       }
 #endif
@@ -4387,7 +4448,7 @@
                }
                row_info->bit_depth = 8;
                row_info->pixel_depth = 32;
-               row_info->rowbytes = row_width * 4;
+               row_info->rowbytes = (size_t)row_width * 4;
                row_info->color_type = 6;
                row_info->channels = 4;
             }
@@ -4395,7 +4456,7 @@
             else
             {
                sp = row + (size_t)row_width - 1;
-               dp = row + (size_t)(row_width * 3) - 1;
+               dp = row + (size_t)row_width * 3 - 1;
                for (i = 0; i < row_width; i++)
                {
                   *dp-- = palette[*sp].blue;
@@ -4406,7 +4467,7 @@
 
                row_info->bit_depth = 8;
                row_info->pixel_depth = 24;
-               row_info->rowbytes = row_width * 3;
+               row_info->rowbytes = (size_t)row_width * 3;
                row_info->color_type = 2;
                row_info->channels = 3;
             }
@@ -4812,8 +4873,8 @@
       {
 #ifdef PNG_TARGET_IMPLEMENTS_EXPAND_PALETTE
          /* Do not call 'png_do_expand_palette' if the SIMD implementation
-          * does it (note that this accomodates SIMD implementations which might
-          * only handle specific cases).
+          * does it. (Note that this accommodates SIMD implementations which
+          * might only handle specific cases.)
           */
          if (!png_target_do_expand_palette(png_ptr, row_info))
 #endif
@@ -4963,13 +5024,8 @@
 
 #ifdef PNG_READ_QUANTIZE_SUPPORTED
    if ((png_ptr->transformations & PNG_QUANTIZE) != 0)
-   {
       png_do_quantize(row_info, png_ptr->row_buf + 1,
           png_ptr->palette_lookup, png_ptr->quantize_index);
-
-      if (row_info->rowbytes == 0)
-         png_error(png_ptr, "png_do_quantize returned rowbytes=0");
-   }
 #endif /* READ_QUANTIZE */
 
 #ifdef PNG_READ_EXPAND_16_SUPPORTED
diff --git a/pngrutil.c b/pngrutil.c
index 39567b8..5dd5361 100644
--- a/pngrutil.c
+++ b/pngrutil.c
@@ -436,17 +436,12 @@
     * be gained by using this when it is known *if* the zlib stream itself does
     * not record the number; however, this is an illusion: the original writer
     * of the PNG may have selected a lower window size, and we really must
-    * follow that because, for systems with with limited capabilities, we
+    * follow that because, for systems with limited capabilities, we
     * would otherwise reject the application's attempts to use a smaller window
     * size (zlib doesn't have an interface to say "this or lower"!).
-    *
-    * inflateReset2 was added to zlib 1.2.4; before this the window could not be
-    * reset, therefore it is necessary to always allocate the maximum window
-    * size with earlier zlibs just in case later compressed chunks need it.
     */
    {
       int ret; /* zlib return code */
-#if ZLIB_VERNUM >= 0x1240
       int window_bits = 0;
 
       if (((png_ptr->options >> PNG_MAXIMUM_INFLATE_WINDOW) & 3) ==
@@ -460,7 +455,6 @@
       {
          png_ptr->zstream_start = 1;
       }
-#endif /* ZLIB_VERNUM >= 0x1240 */
 
       /* Set this for safety, just in case the previous owner left pointers to
        * memory allocations.
@@ -472,20 +466,12 @@
 
       if ((png_ptr->flags & PNG_FLAG_ZSTREAM_INITIALIZED) != 0)
       {
-#if ZLIB_VERNUM >= 0x1240
          ret = inflateReset2(&png_ptr->zstream, window_bits);
-#else
-         ret = inflateReset(&png_ptr->zstream);
-#endif
       }
 
       else
       {
-#if ZLIB_VERNUM >= 0x1240
          ret = inflateInit2(&png_ptr->zstream, window_bits);
-#else
-         ret = inflateInit(&png_ptr->zstream);
-#endif
 
          if (ret == Z_OK)
             png_ptr->flags |= PNG_FLAG_ZSTREAM_INITIALIZED;
@@ -511,7 +497,6 @@
 #endif
 }
 
-#if ZLIB_VERNUM >= 0x1240
 /* Handle the start of the inflate stream if we called inflateInit2(strm,0);
  * in this case some zlib versions skip validation of the CINFO field and, in
  * certain circumstances, libpng may end up displaying an invalid image, in
@@ -534,7 +519,6 @@
 
    return inflate(&png_ptr->zstream, flush);
 }
-#endif /* Zlib >= 1.2.4 */
 
 #ifdef PNG_READ_COMPRESSED_TEXT_SUPPORTED
 #if defined(PNG_READ_zTXt_SUPPORTED) || defined (PNG_READ_iTXt_SUPPORTED)
@@ -569,7 +553,7 @@
        * a performance advantage, because it reduces the amount of data accessed
        * at each step and that may give the OS more time to page it in.
        */
-      png_ptr->zstream.next_in = PNGZ_INPUT_CAST(input);
+      png_ptr->zstream.next_in = input;
       /* avail_in and avail_out are set below from 'size' */
       png_ptr->zstream.avail_in = 0;
       png_ptr->zstream.avail_out = 0;
@@ -630,7 +614,7 @@
           * the previous chunk of input data.  Tell zlib if we have reached the
           * end of the output buffer.
           */
-         ret = PNG_INFLATE(png_ptr, avail_out > 0 ? Z_NO_FLUSH :
+         ret = png_zlib_inflate(png_ptr, avail_out > 0 ? Z_NO_FLUSH :
              (finish ? Z_FINISH : Z_SYNC_FLUSH));
       } while (ret == Z_OK);
 
@@ -662,7 +646,7 @@
        * pointer, which is not owned by the caller, but this is safe; it's only
        * used on errors!
        */
-      png_ptr->zstream.msg = PNGZ_MSG_CAST("zstream unclaimed");
+      png_ptr->zstream.msg = "zstream unclaimed";
       return Z_STREAM_ERROR;
    }
 }
@@ -871,7 +855,7 @@
           * the available output is produced; this allows reading of truncated
           * streams.
           */
-         ret = PNG_INFLATE(png_ptr, *chunk_bytes > 0 ?
+         ret = png_zlib_inflate(png_ptr, *chunk_bytes > 0 ?
              Z_NO_FLUSH : (finish ? Z_FINISH : Z_SYNC_FLUSH));
       }
       while (ret == Z_OK && (*out_size > 0 || png_ptr->zstream.avail_out > 0));
@@ -886,7 +870,7 @@
 
    else
    {
-      png_ptr->zstream.msg = PNGZ_MSG_CAST("zstream unclaimed");
+      png_ptr->zstream.msg = "zstream unclaimed";
       return Z_STREAM_ERROR;
    }
 }
@@ -1008,7 +992,7 @@
     * in the case of an 8-bit display with a decoder which controls the palette.
     *
     * The alternative here is to ignore the error and store the palette anyway;
-    * destroying the tRNS will definately cause problems.
+    * destroying the tRNS will definitely cause problems.
     *
     * NOTE: the case of PNG_COLOR_TYPE_PALETTE need not be considered because
     * the png_handle_ routines for the three 'after PLTE' chunks tRNS, bKGD and
@@ -1055,19 +1039,6 @@
       /* A valid PLTE chunk has been read */
       png_ptr->mode |= PNG_HAVE_PLTE;
 
-      /* TODO: png_set_PLTE has the side effect of setting png_ptr->palette to
-       * its own copy of the palette.  This has the side effect that when
-       * png_start_row is called (this happens after any call to
-       * png_read_update_info) the info_ptr palette gets changed.  This is
-       * extremely unexpected and confusing.
-       *
-       * REVIEW: there have been consistent bugs in the past about gamma and
-       * similar transforms to colour mapped images being useless because the
-       * modified palette cannot be accessed because of the above.
-       *
-       * CONSIDER: Fix this by not sharing the palette in this way.  But does
-       * this completely fix the problem?
-       */
       png_set_PLTE(png_ptr, info_ptr, palette, num);
       return handled_ok;
    }
@@ -1269,7 +1240,7 @@
 
    /* png_set_cHRM may complain about some of the values but this doesn't matter
     * because it was a cHRM and it did have vaguely (if, perhaps, ridiculous)
-    * values.  Ridiculousity will be checked if the values are used later.
+    * values.  Ridiculosity will be checked if the values are used later.
     */
    png_set_cHRM_fixed(png_ptr, info_ptr, xy.whitex, xy.whitey, xy.redx, xy.redy,
          xy.greenx, xy.greeny, xy.bluex, xy.bluey);
@@ -1774,10 +1745,6 @@
       return handled_error;
    }
 
-   /* TODO: this is a horrible side effect in the palette case because the
-    * png_struct ends up with a pointer to the tRNS buffer owned by the
-    * png_info.  Fix this.
-    */
    png_set_tRNS(png_ptr, info_ptr, readbuf, png_ptr->num_trans,
        &(png_ptr->trans_color));
    return handled_ok;
@@ -2036,7 +2003,7 @@
       return handled_error;
 
    /* PNGv3: the code used to check the byte order mark at the start for MM or
-    * II, however PNGv3 states that the the first 4 bytes should be checked.
+    * II, however PNGv3 states that the first 4 bytes should be checked.
     * The caller ensures that there are four bytes available.
     */
    {
@@ -3196,7 +3163,7 @@
    png_uint_32 max_length :12; /* Length min, max in bytes */
    png_uint_32 min_length :8;
       /* Length errors on critical chunks have special handling to preserve the
-       * existing behaviour in libpng 1.6.  Anciallary chunks are checked below
+       * existing behaviour in libpng 1.6.  Ancillary chunks are checked below
        * and produce a 'benign' error.
        */
    png_uint_32 pos_before :4; /* PNG_HAVE_ values chunk must precede */
@@ -3204,7 +3171,7 @@
       /* NOTE: PLTE, tRNS and bKGD require special handling which depends on
        * the colour type of the base image.
        */
-   png_uint_32 multiple   :1; /* Multiple occurences permitted */
+   png_uint_32 multiple   :1; /* Multiple occurrences permitted */
       /* This is enabled for PLTE because PLTE may, in practice, be optional */
 }
 read_chunks[PNG_INDEX_unknown] =
@@ -3238,7 +3205,7 @@
 #  define CDIHDR      13U,   13U,  hIHDR,     0,        0
 #  define CDPLTE  NoCheck,    0U,      0, hIHDR,        1
       /* PLTE errors are only critical for colour-map images, consequently the
-       * hander does all the checks.
+       * handler does all the checks.
        */
 #  define CDIDAT  NoCheck,    0U,  aIDAT, hIHDR,        1
 #  define CDIEND  NoCheck,    0U,      0, aIDAT,        0
@@ -4469,7 +4436,7 @@
        *
        * TODO: deal more elegantly with truncated IDAT lists.
        */
-      ret = PNG_INFLATE(png_ptr, Z_NO_FLUSH);
+      ret = png_zlib_inflate(png_ptr, Z_NO_FLUSH);
 
       /* Take the unconsumed output back. */
       if (output != NULL)
@@ -4823,6 +4790,7 @@
    {
       png_free(png_ptr, png_ptr->big_row_buf);
       png_free(png_ptr, png_ptr->big_prev_row);
+      png_ptr->big_row_buf = png_ptr->big_prev_row = NULL;
 
       if (png_ptr->interlaced != 0)
          png_ptr->big_row_buf = (png_byte *)png_calloc(png_ptr,
diff --git a/pngset.c b/pngset.c
index 8bf9e2d..10eea62 100644
--- a/pngset.c
+++ b/pngset.c
@@ -1,6 +1,6 @@
 /* pngset.c - storage of image information into info struct
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -324,7 +324,8 @@
    png_debug1(1, "in %s storage function", "eXIf");
 
    if (png_ptr == NULL || info_ptr == NULL ||
-       (png_ptr->mode & PNG_WROTE_eXIf) != 0)
+       (png_ptr->mode & PNG_WROTE_eXIf) != 0 ||
+       exif == NULL)
       return;
 
    new_exif = png_voidcast(png_byte *, png_malloc_warn(png_ptr, num_exif));
@@ -375,11 +376,12 @@
 png_set_hIST(const png_struct *png_ptr, png_info *info_ptr,
     const png_uint_16 *hist)
 {
+   png_uint_16 safe_hist[PNG_MAX_PALETTE_LENGTH];
    int i;
 
    png_debug1(1, "in %s storage function", "hIST");
 
-   if (png_ptr == NULL || info_ptr == NULL)
+   if (png_ptr == NULL || info_ptr == NULL || hist == NULL)
       return;
 
    if (info_ptr->num_palette == 0 || info_ptr->num_palette
@@ -391,6 +393,13 @@
       return;
    }
 
+   /* Snapshot the caller's hist before freeing, in case it points to
+    * info_ptr->hist (getter-to-setter aliasing).
+    */
+   memcpy(safe_hist, hist, (unsigned int)info_ptr->num_palette *
+       (sizeof (png_uint_16)));
+   hist = safe_hist;
+
    png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0);
 
    /* Changed from info->num_palette to PNG_MAX_PALETTE_LENGTH in
@@ -739,7 +748,7 @@
 png_set_PLTE(png_struct *png_ptr, png_info *info_ptr,
     const png_color *palette, int num_palette)
 {
-
+   png_color safe_palette[PNG_MAX_PALETTE_LENGTH];
    png_uint_32 max_palette_length;
 
    png_debug1(1, "in %s storage function", "PLTE");
@@ -773,28 +782,48 @@
       png_error(png_ptr, "Invalid palette");
    }
 
-   /* It may not actually be necessary to set png_ptr->palette here;
-    * we do it for backward compatibility with the way the png_handle_tRNS
-    * function used to do the allocation.
-    *
-    * 1.6.0: the above statement appears to be incorrect; something has to set
-    * the palette inside png_struct on read.
+   /* Snapshot the caller's palette before freeing, in case it points to
+    * info_ptr->palette (getter-to-setter aliasing).
     */
+   if (num_palette > 0)
+      memcpy(safe_palette, palette, (unsigned int)num_palette *
+          (sizeof (png_color)));
+
+   palette = safe_palette;
+
    png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0);
 
    /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead
     * of num_palette entries, in case of an invalid PNG file or incorrect
     * call to png_set_PLTE() with too-large sample values.
+    *
+    * Allocate independent buffers for info_ptr and png_ptr so that the
+    * lifetime of png_ptr->palette is decoupled from the lifetime of
+    * info_ptr->palette.  Previously, these two pointers were aliased,
+    * which caused a use-after-free vulnerability if png_free_data freed
+    * info_ptr->palette while png_ptr->palette was still in use by the
+    * row transform functions (e.g. png_do_expand_palette).
+    *
+    * Both buffers are allocated with png_calloc to zero-fill, because
+    * the ARM NEON palette riffle reads all 256 entries unconditionally,
+    * regardless of num_palette.
     */
+   png_free(png_ptr, png_ptr->palette);
+   png_ptr->palette = NULL;
    png_ptr->palette = png_voidcast(png_color *, png_calloc(png_ptr,
        PNG_MAX_PALETTE_LENGTH * (sizeof (png_color))));
+   info_ptr->palette = png_voidcast(png_colorp, png_calloc(png_ptr,
+       PNG_MAX_PALETTE_LENGTH * (sizeof (png_color))));
+   png_ptr->num_palette = info_ptr->num_palette = (png_uint_16)num_palette;
 
    if (num_palette > 0)
+   {
+      memcpy(info_ptr->palette, palette, (unsigned int)num_palette *
+          (sizeof (png_color)));
       memcpy(png_ptr->palette, palette, (unsigned int)num_palette *
           (sizeof (png_color)));
+   }
 
-   info_ptr->palette = png_ptr->palette;
-   info_ptr->num_palette = png_ptr->num_palette = (png_uint_16)num_palette;
    info_ptr->free_me |= PNG_FREE_PLTE;
    info_ptr->valid |= PNG_INFO_PLTE;
 }
@@ -924,6 +953,7 @@
     const png_text *text_ptr, int num_text)
 {
    int i;
+   png_textp old_text = NULL;
 
    png_debug1(1, "in text storage function, chunk typeid = 0x%lx",
       png_ptr == NULL ? 0xabadca11UL : (unsigned long)png_ptr->chunk_name);
@@ -971,7 +1001,10 @@
          return 1;
       }
 
-      png_free(png_ptr, info_ptr->text);
+      /* Defer freeing the old array until after the copy loop below,
+       * in case text_ptr aliases info_ptr->text (getter-to-setter).
+       */
+      old_text = info_ptr->text;
 
       info_ptr->text = new_text;
       info_ptr->free_me |= PNG_FREE_TEXT;
@@ -1056,6 +1089,7 @@
       {
          png_chunk_report(png_ptr, "text chunk: out of memory",
              PNG_CHUNK_WRITE_ERROR);
+         png_free(png_ptr, old_text);
 
          return 1;
       }
@@ -1109,6 +1143,8 @@
       png_debug1(3, "transferred text chunk %d", info_ptr->num_text);
    }
 
+   png_free(png_ptr, old_text);
+
    return 0;
 }
 #endif
@@ -1152,28 +1188,51 @@
 
    if (trans_alpha != NULL)
    {
-       /* It may not actually be necessary to set png_ptr->trans_alpha here;
-        * we do it for backward compatibility with the way the png_handle_tRNS
-        * function used to do the allocation.
-        *
-        * 1.6.0: The above statement is incorrect; png_handle_tRNS effectively
-        * relies on png_set_tRNS storing the information in png_struct
-        * (otherwise it won't be there for the code in pngrtran.c).
+       /* Snapshot the caller's trans_alpha before freeing, in case it
+        * points to info_ptr->trans_alpha (getter-to-setter aliasing).
         */
+       png_byte safe_trans[PNG_MAX_PALETTE_LENGTH];
+
+       if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH)
+          memcpy(safe_trans, trans_alpha, (size_t)num_trans);
+
+       trans_alpha = safe_trans;
 
        png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
 
        if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH)
        {
-         /* Changed from num_trans to PNG_MAX_PALETTE_LENGTH in version 1.2.1 */
+          /* Allocate info_ptr's copy of the transparency data.
+           * Initialize all entries to fully opaque (0xff), then overwrite
+           * the first num_trans entries with the actual values.
+           */
           info_ptr->trans_alpha = png_voidcast(png_byte *,
               png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH));
+          memset(info_ptr->trans_alpha, 0xff, PNG_MAX_PALETTE_LENGTH);
           memcpy(info_ptr->trans_alpha, trans_alpha, (size_t)num_trans);
-
           info_ptr->free_me |= PNG_FREE_TRNS;
           info_ptr->valid |= PNG_INFO_tRNS;
+
+          /* Allocate an independent copy for png_struct, so that the
+           * lifetime of png_ptr->trans_alpha is decoupled from the
+           * lifetime of info_ptr->trans_alpha.  Previously these two
+           * pointers were aliased, which caused a use-after-free if
+           * png_free_data freed info_ptr->trans_alpha while
+           * png_ptr->trans_alpha was still in use by the row transform
+           * functions (e.g. png_do_expand_palette).
+           */
+          png_free(png_ptr, png_ptr->trans_alpha);
+          png_ptr->trans_alpha = NULL;
+          png_ptr->trans_alpha = png_voidcast(png_bytep,
+              png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH));
+          memset(png_ptr->trans_alpha, 0xff, PNG_MAX_PALETTE_LENGTH);
+          memcpy(png_ptr->trans_alpha, trans_alpha, (size_t)num_trans);
        }
-       png_ptr->trans_alpha = info_ptr->trans_alpha;
+       else
+       {
+          png_free(png_ptr, png_ptr->trans_alpha);
+          png_ptr->trans_alpha = NULL;
+       }
    }
 
    if (trans_color != NULL)
@@ -1224,6 +1283,7 @@
  */
 {
    png_sPLT_t *np;
+   png_sPLT_t *old_spalettes;
 
    png_debug1(1, "in %s storage function", "sPLT");
 
@@ -1244,7 +1304,10 @@
       return;
    }
 
-   png_free(png_ptr, info_ptr->splt_palettes);
+   /* Defer freeing the old array until after the copy loop below,
+    * in case entries aliases info_ptr->splt_palettes (getter-to-setter).
+    */
+   old_spalettes = info_ptr->splt_palettes;
 
    info_ptr->splt_palettes = np;
    info_ptr->free_me |= PNG_FREE_SPLT;
@@ -1308,6 +1371,8 @@
    }
    while (--nentries);
 
+   png_free(png_ptr, old_spalettes);
+
    if (nentries > 0)
       png_chunk_report(png_ptr, "sPLT out of memory", PNG_CHUNK_WRITE_ERROR);
 }
@@ -1494,6 +1559,7 @@
     png_info *info_ptr, const png_unknown_chunk *unknowns, int num_unknowns)
 {
    png_unknown_chunk *np;
+   png_unknown_chunk *old_unknowns;
 
    if (png_ptr == NULL || info_ptr == NULL || num_unknowns <= 0 ||
        unknowns == NULL)
@@ -1540,7 +1606,10 @@
       return;
    }
 
-   png_free(png_ptr, info_ptr->unknown_chunks);
+   /* Defer freeing the old array until after the copy loop below,
+    * in case unknowns aliases info_ptr->unknown_chunks (getter-to-setter).
+    */
+   old_unknowns = info_ptr->unknown_chunks;
 
    info_ptr->unknown_chunks = np; /* safe because it is initialized */
    info_ptr->free_me |= PNG_FREE_UNKN;
@@ -1586,6 +1655,8 @@
       ++np;
       ++(info_ptr->unknown_chunks_num);
    }
+
+   png_free(png_ptr, old_unknowns);
 }
 
 void
@@ -2012,7 +2083,7 @@
 #endif /* BENIGN_ERRORS */
 
 #ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED
-   /* Whether to report invalid palette index; added at libng-1.5.10.
+   /* Whether to report invalid palette index; added at libpng-1.5.10.
     * It is possible for an indexed (color-type==3) PNG file to contain
     * pixels with invalid (out-of-range) indexes if the PLTE chunk has
     * fewer entries than the image's bit-depth would allow. We recover
diff --git a/pngsimd.c b/pngsimd.c
index c83e3b8..7bd7b53 100644
--- a/pngsimd.c
+++ b/pngsimd.c
@@ -25,7 +25,7 @@
  *    png_target_impl
  *       string constant
  *       REQUIRED
- *       This must be a string naming the implemenation.
+ *       This must be a string naming the implementation.
  *
  *    png_target_free_data_impl
  *       static void png_target_free_data_impl(png_struct *)
diff --git a/pngstruct.h b/pngstruct.h
index 224009e..0bdeebf 100644
--- a/pngstruct.h
+++ b/pngstruct.h
@@ -1,6 +1,6 @@
 /* pngstruct.h - internal structures for libpng
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -30,17 +30,6 @@
 #  undef const
 #endif
 
-/* zlib.h has mediocre z_const use before 1.2.6, this stuff is for compatibility
- * with older builds.
- */
-#if ZLIB_VERNUM < 0x1260
-#  define PNGZ_MSG_CAST(s) png_constcast(char*,s)
-#  define PNGZ_INPUT_CAST(b) png_constcast(png_byte *,b)
-#else
-#  define PNGZ_MSG_CAST(s) (s)
-#  define PNGZ_INPUT_CAST(b) (b)
-#endif
-
 /* zlib.h declares a magic type 'uInt' that limits the amount of data that zlib
  * can handle at once.  This type need be no larger than 16 bits (so maximum of
  * 65535), this define allows us to discover how big it is, but limited by the
@@ -60,7 +49,6 @@
    struct png_compression_buffer *next;
    png_byte                       output[1]; /* actually zbuf_size */
 } png_compression_buffer;
-typedef png_compression_buffer *png_compression_bufferp; /* [Deprecated] */
 
 #define PNG_COMPRESSION_BUFFER_SIZE(pp)\
    (offsetof(png_compression_buffer, output) + (pp)->zbuffer_size)
@@ -107,7 +95,7 @@
  * TODO: C23: convert these macros to C23 inlines (which are static).
  */
 #define png_chunk_flag_from_index(i) (0x80000000U >> (31 - (i)))
-   /* The flag coresponding to the given png_index enum value.  This is defined
+   /* The flag corresponding to the given png_index enum value.  This is defined
     * for png_unknown as well (until it reaches the value 32) but this should
     * not be relied on.
     */
@@ -116,7 +104,7 @@
    (((png_ptr)->chunks & png_chunk_flag_from_index(i)) != 0)
    /* The chunk has been recorded in png_struct */
 
-#define png_file_add_chunk(pnt_ptr, i)\
+#define png_file_add_chunk(png_ptr, i)\
    ((void)((png_ptr)->chunks |= png_chunk_flag_from_index(i)))
    /* Record the chunk in the png_struct */
 
@@ -248,9 +236,7 @@
                               /* pixel depth used for the row buffers */
    png_byte transformed_pixel_depth;
                               /* pixel depth after read/write transforms */
-#if ZLIB_VERNUM >= 0x1240
    png_byte zstream_start;    /* at start of an input zlib stream */
-#endif /* Zlib >= 1.2.4 */
 #if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED)
    png_uint_16 filler;           /* filler bytes for pixel expansion */
 #endif
@@ -411,7 +397,6 @@
 
 #ifdef PNG_READ_QUANTIZE_SUPPORTED
 /* The following three members were added at version 1.0.14 and 1.2.4 */
-   png_byte *quantize_sort;          /* working sort array */
    png_byte *index_to_palette;       /* where the original index currently is
                                         in the palette */
    png_byte *palette_to_index;       /* which original index points to this
diff --git a/pngtarget.h b/pngtarget.h
index f80d0be..162d5c7 100644
--- a/pngtarget.h
+++ b/pngtarget.h
@@ -29,7 +29,7 @@
  *    PNG_TARGET_CODE_IMPLEMENTATION
  *
  * To the quoted relative path name of a single C file to include to obtain the
- * implementation of the the target specific code.  For example:
+ * implementation of the target specific code.  For example:
  *
  *    "arm/arm_init.c"
  *    "intel/intel_init.c"
diff --git a/pngtest.c b/pngtest.c
index 2caade4..f1ff6eb 100644
--- a/pngtest.c
+++ b/pngtest.c
@@ -1,6 +1,6 @@
 /* pngtest.c - a test program for libpng
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -50,9 +50,6 @@
  */
 #define STDERR stdout
 
-/* Generate a compiler error if there is an old png.h in the search path. */
-typedef png_libpng_version_1_8_0_git Your_png_h_is_not_version_1_8_0_git;
-
 /* Ensure that all version numbers in png.h are consistent with one another. */
 #if (PNG_LIBPNG_VER != PNG_LIBPNG_VER_MAJOR * 10000 + \
                        PNG_LIBPNG_VER_MINOR * 100 + \
diff --git a/pngtrans.c b/pngtrans.c
index a460726..a6cab0c 100644
--- a/pngtrans.c
+++ b/pngtrans.c
@@ -1,6 +1,6 @@
 /* pngtrans.c - transforms the data in a row (used by both readers and writers)
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -84,9 +84,38 @@
 {
    png_debug(1, "in png_set_shift");
 
-   if (png_ptr == NULL)
+   if (png_ptr == NULL || true_bits == NULL)
       return;
 
+   /* Check the shift values before passing them on to png_do_shift. */
+   {
+      png_byte bit_depth = png_ptr->bit_depth;
+      int invalid = 0;
+
+      if ((png_ptr->color_type & PNG_COLOR_MASK_COLOR) != 0)
+      {
+         if (true_bits->red == 0 || true_bits->red > bit_depth ||
+             true_bits->green == 0 || true_bits->green > bit_depth ||
+             true_bits->blue == 0 || true_bits->blue > bit_depth)
+            invalid = 1;
+      }
+      else
+      {
+         if (true_bits->gray == 0 || true_bits->gray > bit_depth)
+            invalid = 1;
+      }
+
+      if ((png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0 &&
+          (true_bits->alpha == 0 || true_bits->alpha > bit_depth))
+         invalid = 1;
+
+      if (invalid)
+      {
+         png_app_error(png_ptr, "png_set_shift: invalid shift values");
+         return;
+      }
+   }
+
    png_ptr->transformations |= PNG_SHIFT;
    png_ptr->shift = *true_bits;
 }
diff --git a/pngwrite.c b/pngwrite.c
index 51d6140..f067ec1 100644
--- a/pngwrite.c
+++ b/pngwrite.c
@@ -1,6 +1,6 @@
 /* pngwrite.c - general routines to write a PNG file
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -164,7 +164,7 @@
     * them.
     *
     * PNG v3: Chunks mDCV and cLLI provide ancillary information for the
-    * interpretation of the colourspace chunkgs but do not require support for
+    * interpretation of the colourspace chunks but do not require support for
     * those chunks so are outside the "COLORSPACE" check but before the write of
     * the colourspace chunks themselves.
     */
@@ -1020,6 +1020,16 @@
    png_ptr->chunk_list = NULL;
 #endif
 
+#if defined(PNG_tRNS_SUPPORTED)
+   /* Free the independent copy of trans_alpha owned by png_struct. */
+   png_free(png_ptr, png_ptr->trans_alpha);
+   png_ptr->trans_alpha = NULL;
+#endif
+
+   /* Free the independent copy of the palette owned by png_struct. */
+   png_free(png_ptr, png_ptr->palette);
+   png_ptr->palette = NULL;
+
    /* The error handling and memory handling information is left intact at this
     * point: the jmp_buf may still have to be freed.  See png_destroy_png_struct
     * for how this happens.
@@ -1578,16 +1588,18 @@
 /* Arguments to png_image_write_main: */
 typedef struct
 {
-   /* Arguments: */
+   /* Arguments */
    png_image *image;
    const void *buffer;
    png_int_32 row_stride;
    const void *colormap;
    int convert_to_8bit;
-   /* Local variables: */
+
+   /* Instance variables */
    const void *first_row;
-   ptrdiff_t row_bytes;
    void *local_row;
+   ptrdiff_t row_step;
+
    /* Byte count for memory writing */
    png_byte *memory;
    png_alloc_size_t memory_bytes; /* not used for STDIO */
@@ -1697,7 +1709,7 @@
 
       png_write_row(png_ptr,
           png_voidcast(const png_byte *, display->local_row));
-      input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16));
+      input_row += display->row_step / 2;
    }
 
    return 1;
@@ -1823,7 +1835,7 @@
 
          png_write_row(png_ptr, png_voidcast(const png_byte *,
              display->local_row));
-         input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16));
+         input_row += display->row_step / 2;
       } /* while y */
    }
 
@@ -1848,7 +1860,7 @@
          }
 
          png_write_row(png_ptr, output_row);
-         input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16));
+         input_row += display->row_step / 2;
       }
    }
 
@@ -2037,7 +2049,7 @@
             display->row_stride = (png_int_32)/*SAFE*/png_row_stride;
 
          if (display->row_stride < 0)
-            check = (png_uint_32)(-display->row_stride);
+            check = -(png_uint_32)display->row_stride;
 
          else
             check = (png_uint_32)display->row_stride;
@@ -2164,16 +2176,16 @@
 
    {
       const png_byte *row = png_voidcast(const png_byte *, display->buffer);
-      ptrdiff_t row_bytes = display->row_stride;
+      ptrdiff_t row_step = display->row_stride;
 
       if (linear != 0)
-         row_bytes *= (sizeof (png_uint_16));
+         row_step *= 2;
 
-      if (row_bytes < 0)
-         row += (image->height-1) * (-row_bytes);
+      if (row_step < 0)
+         row += (image->height-1) * (-row_step);
 
       display->first_row = row;
-      display->row_bytes = row_bytes;
+      display->row_step = row_step;
    }
 
    /* Apply 'fast' options if the flag is set. */
@@ -2194,8 +2206,7 @@
     * before it is written.  This only applies when the input is 16-bit and
     * either there is an alpha channel or it is converted to 8-bit.
     */
-   if ((linear != 0 && alpha != 0 ) ||
-       (colormap == 0 && display->convert_to_8bit != 0))
+   if (linear != 0 && (alpha != 0 || display->convert_to_8bit != 0))
    {
       png_byte *row = png_voidcast(png_byte *, png_malloc(png_ptr,
           png_get_rowbytes(png_ptr, info_ptr)));
@@ -2221,13 +2232,13 @@
    else
    {
       const png_byte *row = png_voidcast(const png_byte *, display->first_row);
-      ptrdiff_t row_bytes = display->row_bytes;
+      ptrdiff_t row_step = display->row_step;
       png_uint_32 y = image->height;
 
       for (; y > 0; --y)
       {
          png_write_row(png_ptr, row);
-         row += row_bytes;
+         row += row_step;
       }
    }
 
diff --git a/pngwutil.c b/pngwutil.c
index a8e72f2..dae8850 100644
--- a/pngwutil.c
+++ b/pngwutil.c
@@ -1,6 +1,6 @@
 /* pngwutil.c - utilities to write a PNG file
  *
- * Copyright (c) 2018-2025 Cosmin Truta
+ * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
  * Copyright (c) 1996-1997 Andreas Dilger
  * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
@@ -330,7 +330,7 @@
          /* Attempt sane error recovery */
          if (png_ptr->zowner == png_IDAT) /* don't steal from IDAT */
          {
-            png_ptr->zstream.msg = PNGZ_MSG_CAST("in use by IDAT");
+            png_ptr->zstream.msg = "in use by IDAT";
             return Z_STREAM_ERROR;
          }
 
@@ -534,7 +534,7 @@
       png_uint_32 output_len;
 
       /* zlib updates these for us: */
-      png_ptr->zstream.next_in = PNGZ_INPUT_CAST(comp->input);
+      png_ptr->zstream.next_in = comp->input;
       png_ptr->zstream.avail_in = 0; /* Set below */
       png_ptr->zstream.next_out = comp->output;
       png_ptr->zstream.avail_out = (sizeof comp->output);
@@ -618,7 +618,7 @@
        */
       if (output_len + prefix_len >= PNG_UINT_31_MAX)
       {
-         png_ptr->zstream.msg = PNGZ_MSG_CAST("compressed data too long");
+         png_ptr->zstream.msg = "compressed data too long";
          ret = Z_MEM_ERROR;
       }
 
@@ -903,7 +903,6 @@
    png_debug1(3, "num_palette = %d", png_ptr->num_palette);
 
    png_write_chunk_header(png_ptr, png_PLTE, (png_uint_32)(num_pal * 3));
-#ifdef PNG_POINTER_INDEXING_SUPPORTED
 
    for (i = 0, pal_ptr = palette; i < num_pal; i++, pal_ptr++)
    {
@@ -913,21 +912,6 @@
       png_write_chunk_data(png_ptr, buf, 3);
    }
 
-#else
-   /* This is a little slower but some buggy compilers need to do this
-    * instead
-    */
-   pal_ptr=palette;
-
-   for (i = 0; i < num_pal; i++)
-   {
-      buf[0] = pal_ptr[i].red;
-      buf[1] = pal_ptr[i].green;
-      buf[2] = pal_ptr[i].blue;
-      png_write_chunk_data(png_ptr, buf, 3);
-   }
-
-#endif
    png_write_chunk_end(png_ptr);
    png_ptr->mode |= PNG_HAVE_PLTE;
 }
@@ -985,7 +969,7 @@
     * terminates the operation.  The _out values are maintained across calls to
     * this function, but the input must be reset each time.
     */
-   png_ptr->zstream.next_in = PNGZ_INPUT_CAST(input);
+   png_ptr->zstream.next_in = input;
    png_ptr->zstream.avail_in = 0; /* set below */
    for (;;)
    {
@@ -1226,9 +1210,6 @@
    size_t entry_size = (spalette->depth == 8 ? 6 : 10);
    size_t palette_size = entry_size * (size_t)spalette->nentries;
    png_sPLT_entry *ep;
-#ifndef PNG_POINTER_INDEXING_SUPPORTED
-   int i;
-#endif
 
    png_debug(1, "in png_write_sPLT");
 
@@ -1246,7 +1227,6 @@
    png_write_chunk_data(png_ptr, &spalette->depth, 1);
 
    /* Loop through each palette entry, writing appropriately */
-#ifdef PNG_POINTER_INDEXING_SUPPORTED
    for (ep = spalette->entries; ep<spalette->entries + spalette->nentries; ep++)
    {
       if (spalette->depth == 8)
@@ -1269,31 +1249,6 @@
 
       png_write_chunk_data(png_ptr, entrybuf, entry_size);
    }
-#else
-   ep=spalette->entries;
-   for (i = 0; i>spalette->nentries; i++)
-   {
-      if (spalette->depth == 8)
-      {
-         entrybuf[0] = (png_byte)ep[i].red;
-         entrybuf[1] = (png_byte)ep[i].green;
-         entrybuf[2] = (png_byte)ep[i].blue;
-         entrybuf[3] = (png_byte)ep[i].alpha;
-         png_save_uint_16(entrybuf + 4, ep[i].frequency);
-      }
-
-      else
-      {
-         png_save_uint_16(entrybuf + 0, ep[i].red);
-         png_save_uint_16(entrybuf + 2, ep[i].green);
-         png_save_uint_16(entrybuf + 4, ep[i].blue);
-         png_save_uint_16(entrybuf + 6, ep[i].alpha);
-         png_save_uint_16(entrybuf + 8, ep[i].frequency);
-      }
-
-      png_write_chunk_data(png_ptr, entrybuf, entry_size);
-   }
-#endif
 
    png_write_chunk_end(png_ptr);
 }
@@ -1763,7 +1718,7 @@
    }
 
    new_key[++key_len] = PNG_COMPRESSION_TYPE_BASE;
-   ++key_len; /* for the keywod separator */
+   ++key_len; /* for the keyword separator */
 
    /* We leave it to the application to meet PNG-1.0 requirements on the
     * contents of the text.  PNG-1.0 through PNG-1.2 discourage the use of
diff --git a/riscv/filter_rvv_intrinsics.c b/riscv/filter_rvv_intrinsics.c
index 1ab4e10..7076276 100644
--- a/riscv/filter_rvv_intrinsics.c
+++ b/riscv/filter_rvv_intrinsics.c
@@ -2,8 +2,11 @@
  *
  * Copyright (c) 2023 Google LLC
  * Written by Manfred SCHLAEGL, 2022
- *            Dragoș Tiselice <dtiselice@google.com>, May 2023.
- *            Filip Wasil     <f.wasil@samsung.com>, March 2025.
+ * Revised by:
+ *  - Dragoș Tiselice <dtiselice@google.com>, May 2023
+ *  - Filip Wasil <f.wasil@samsung.com>, March 2025
+ *  - Liang Junzhao <junzhao.liang@spacemit.com>, November 2025
+ *  - Alexander Smorkalov <alexander.smorkalov@opencv.ai>, December 2025
  *
  * This code is released under the libpng license.
  * For conditions of distribution and use, see the disclaimer
@@ -140,11 +143,8 @@
       /* x = *row */
       x = __riscv_vle8_v_u8m1(row, vl);
 
-      /* tmp = a + b */
-      vuint16m2_t tmp = __riscv_vwaddu_vv_u16m2(a, b, vl);
-
-      /* a = tmp/2 */
-      a = __riscv_vnsrl_wx_u8m1(tmp, 1, vl);
+      /* a = (a + b) / 2, round to zero with vxrm = 2 */
+      a = __riscv_vaaddu_vv_u8m1(a, b, 2, vl);
 
       /* a += x */
       a = __riscv_vadd_vv_u8m1(a, x, vl);
@@ -177,47 +177,6 @@
    PNG_UNUSED(prev_row)
 }
 
-#define MIN_CHUNK_LEN 256
-#define MAX_CHUNK_LEN 2048
-
-static inline vuint8m1_t
-prefix_sum(vuint8m1_t chunk, unsigned char *carry, size_t vl,
-    size_t max_chunk_len)
-{
-   size_t r;
-
-   for (r = 1; r < MIN_CHUNK_LEN; r <<= 1)
-   {
-      vbool8_t shift_mask = __riscv_vmsgeu_vx_u8m1_b8(__riscv_vid_v_u8m1(vl), r, vl);
-      chunk = __riscv_vadd_vv_u8m1_mu(shift_mask, chunk, chunk, __riscv_vslideup_vx_u8m1(__riscv_vundefined_u8m1(), chunk, r, vl), vl);
-   }
-
-   for (r = MIN_CHUNK_LEN; r < MAX_CHUNK_LEN && r < max_chunk_len; r <<= 1)
-   {
-      vbool8_t shift_mask = __riscv_vmsgeu_vx_u8m1_b8(__riscv_vid_v_u8m1(vl), r, vl);
-      chunk = __riscv_vadd_vv_u8m1_mu(shift_mask, chunk, chunk, __riscv_vslideup_vx_u8m1(__riscv_vundefined_u8m1(), chunk, r, vl), vl);
-   }
-
-   chunk = __riscv_vadd_vx_u8m1(chunk, *carry, vl);
-   *carry = __riscv_vmv_x_s_u8m1_u8(__riscv_vslidedown_vx_u8m1(chunk, vl - 1, vl));
-
-   return chunk;
-}
-
-static inline vint16m1_t
-abs_diff(vuint16m1_t a, vuint16m1_t b, size_t vl)
-{
-   vint16m1_t diff = __riscv_vreinterpret_v_u16m1_i16m1(__riscv_vsub_vv_u16m1(a, b, vl));
-   vbool16_t mask = __riscv_vmslt_vx_i16m1_b16(diff, 0, vl);
-   return __riscv_vrsub_vx_i16m1_m(mask, diff, 0, vl);
-}
-
-static inline vint16m1_t
-abs_sum(vint16m1_t a, vint16m1_t b, size_t vl)
-{
-   return __riscv_vadd_vv_i16m1(a, b, vl);
-}
-
 static inline void
 png_read_filter_row_paeth_rvv(size_t len, size_t bpp, unsigned char *row,
     const unsigned char *prev)
@@ -265,27 +224,22 @@
       /* x = *row */
       vuint8m1_t x = __riscv_vle8_v_u8m1(row, vl);
 
-      /* Calculate p = b - c and pc = a - c using widening subtraction */
-      vuint16m2_t p_wide = __riscv_vwsubu_vv_u16m2(b, c, vl);
-      vuint16m2_t pc_wide = __riscv_vwsubu_vv_u16m2(a, c, vl);
-
-      /* Convert to signed for easier manipulation */
-      size_t vl16 = __riscv_vsetvl_e16m2(bpp);
-      vint16m2_t p = __riscv_vreinterpret_v_u16m2_i16m2(p_wide);
-      vint16m2_t pc = __riscv_vreinterpret_v_u16m2_i16m2(pc_wide);
+      /* p = b - c and pc = a - c */
+      vuint16m2_t p = __riscv_vwsubu_vv_u16m2(b, c, vl);
+      vuint16m2_t pc = __riscv_vwsubu_vv_u16m2(a, c, vl);
 
       /* pa = |p| */
-      vbool8_t p_neg_mask = __riscv_vmslt_vx_i16m2_b8(p, 0, vl16);
-      vint16m2_t pa = __riscv_vrsub_vx_i16m2_m(p_neg_mask, p, 0, vl16);
+      vuint16m2_t tmp = __riscv_vrsub_vx_u16m2(p, 0, vl);
+      vuint16m2_t pa = __riscv_vminu_vv_u16m2(p, tmp, vl);
 
       /* pb = |pc| */
-      vbool8_t pc_neg_mask = __riscv_vmslt_vx_i16m2_b8(pc, 0, vl16);
-      vint16m2_t pb = __riscv_vrsub_vx_i16m2_m(pc_neg_mask, pc, 0, vl16);
+      tmp = __riscv_vrsub_vx_u16m2(pc, 0, vl);
+      vuint16m2_t pb = __riscv_vminu_vv_u16m2(pc, tmp, vl);
 
       /* pc = |p + pc| */
-      vint16m2_t p_plus_pc = __riscv_vadd_vv_i16m2(p, pc, vl16);
-      vbool8_t p_plus_pc_neg_mask = __riscv_vmslt_vx_i16m2_b8(p_plus_pc, 0, vl16);
-      pc = __riscv_vrsub_vx_i16m2_m(p_plus_pc_neg_mask, p_plus_pc, 0, vl16);
+      pc = __riscv_vadd_vv_u16m2(p, pc, vl);
+      tmp = __riscv_vrsub_vx_u16m2(pc, 0, vl);
+      pc = __riscv_vminu_vv_u16m2(pc, tmp, vl);
 
       /*
        * The key insight is that we want the minimum of pa, pb, pc.
@@ -294,31 +248,17 @@
        * - Else use c
        */
 
-      /* Find which predictor to use based on minimum absolute difference */
-      vbool8_t pa_le_pb = __riscv_vmsle_vv_i16m2_b8(pa, pb, vl16);
-      vbool8_t pa_le_pc = __riscv_vmsle_vv_i16m2_b8(pa, pc, vl16);
-      vbool8_t pb_le_pc = __riscv_vmsle_vv_i16m2_b8(pb, pc, vl16);
+      /* if (pb < pa) { pa = pb; a = b; } */
+      vbool8_t m1 = __riscv_vmsltu_vv_u16m2_b8(pb, pa, vl);
+      pa = __riscv_vmerge_vvm_u16m2(pa, pb, m1, vl);
+      a = __riscv_vmerge_vvm_u8m1(a, b, m1, vl);
 
-      /* use_a = pa <= pb && pa <= pc */
-      vbool8_t use_a = __riscv_vmand_mm_b8(pa_le_pb, pa_le_pc, vl16);
-
-      /* use_b = !use_a && pb <= pc */
-      vbool8_t not_use_a = __riscv_vmnot_m_b8(use_a, vl16);
-      vbool8_t use_b = __riscv_vmand_mm_b8(not_use_a, pb_le_pc, vl16);
-
-      /* Switch back to e8m1 for final operations */
-      vl = __riscv_vsetvl_e8m1(bpp);
-
-      /* Start with a, then conditionally replace with b or c */
-      vuint8m1_t result = a;
-      result = __riscv_vmerge_vvm_u8m1(result, b, use_b, vl);
-
-      /* use_c = !use_a && !use_b */
-      vbool8_t use_c = __riscv_vmnand_mm_b8(__riscv_vmor_mm_b8(use_a, use_b, vl), __riscv_vmor_mm_b8(use_a, use_b, vl), vl);
-      result = __riscv_vmerge_vvm_u8m1(result, c, use_c, vl);
+      /* if (pc < pa) a = c; */
+      vbool8_t m2 = __riscv_vmsltu_vv_u16m2_b8(pc, pa, vl);
+      a = __riscv_vmerge_vvm_u8m1(a, c, m2, vl);
 
       /* a = result + x */
-      a = __riscv_vadd_vv_u8m1(result, x, vl);
+      a = __riscv_vadd_vv_u8m1(a, x, vl);
 
       /* *row = a */
       __riscv_vse8_v_u8m1(row, a, vl);
diff --git a/scripts/cmake/AUTHORS.md b/scripts/cmake/AUTHORS.md
index c25a505..0f9d4e9 100644
--- a/scripts/cmake/AUTHORS.md
+++ b/scripts/cmake/AUTHORS.md
@@ -11,7 +11,9 @@
  * B. Scott Michel
  * Benjamin Buch
  * Bernd Kuhls
+ * Brad King
  * Cameron Cawley
+ * Carlo Bramini
  * Christian Ehrlicher
  * Christopher Sean Morrison
  * Claudio Bley
@@ -30,7 +32,9 @@
  * John Bowler
  * Jon Creighton
  * Joost Nieuwenhuijse
+ * Kakeyama Yaito (掛山夜糸)
  * Kyle Bentley
+ * Luis Caro Campos
  * Martin Storsjö
  * Owen Rudge
  * Philip Lowman
diff --git a/scripts/cmake/README.md b/scripts/cmake/README.md
index 18e7107..e3b00a4 100644
--- a/scripts/cmake/README.md
+++ b/scripts/cmake/README.md
@@ -4,7 +4,7 @@
 Copyright Notice
 ----------------
 
- * Copyright (c) 2018-2024 Cosmin Truta.
+ * Copyright (c) 2018-2026 Cosmin Truta.
  * Copyright (c) 2007-2018 Glenn Randers-Pehrson.
  * Originally written by Christian Ehrlicher, 2007.
 
@@ -12,7 +12,7 @@
 files in the libpng distribution are subject to
 the same licensing terms and conditions as libpng.
 Please see the copyright notice in `png.h` or visit
-http://libpng.org/pub/png/src/libpng-LICENSE.txt
+<http://libpng.org/pub/png/src/libpng-LICENSE.txt>
 
 File List
 ---------
diff --git a/scripts/pnglibconf/pnglibconf.dfa b/scripts/pnglibconf/pnglibconf.dfa
index 8e29e7e..1a79041 100644
--- a/scripts/pnglibconf/pnglibconf.dfa
+++ b/scripts/pnglibconf/pnglibconf.dfa
@@ -8,7 +8,7 @@
 com
 version
 com
-com Copyright (c) 2018-2025 Cosmin Truta
+com Copyright (c) 2018-2026 Cosmin Truta
 com Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
 com
 com This code is released under the libpng license.
@@ -614,11 +614,6 @@
 
 setting sRGB_PROFILE_CHECKS default 2
 
-# Buggy compilers (e.g., gcc 2.7.2.2) need PNG_NO_POINTER_INDEXING
-# See png[wr]util.c, normally this should always be *on*
-
-option POINTER_INDEXING
-
 # Other defines for things like memory and the like can go here.
 
 # BUILD TIME SETTINGS
diff --git a/tests/pnggetset b/tests/pnggetset
new file mode 100755
index 0000000..57ef731
--- /dev/null
+++ b/tests/pnggetset
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# pnggetset test:
+# Getter-to-setter roundtrips for various chunk types.
+exec ./pnggetset
diff --git a/tests/pngimage-full b/tests/pngimage-full
index 1198ed0..b87b7ce 100755
--- a/tests/pngimage-full
+++ b/tests/pngimage-full
@@ -1,2 +1,5 @@
 #!/bin/sh
+
+# pngimage-full test:
+# Exhaustive read validation against the pngsuite corpus.
 exec ./pngimage --exhaustive --list-combos --log "${srcdir}/contrib/pngsuite/"*.png
diff --git a/tests/pngimage-quick b/tests/pngimage-quick
index 328dc27..705e4fa 100755
--- a/tests/pngimage-quick
+++ b/tests/pngimage-quick
@@ -1,2 +1,5 @@
 #!/bin/sh
+
+# pngimage-quick test:
+# Quick read validation against the pngsuite corpus.
 exec ./pngimage --list-combos --log "${srcdir}/contrib/pngsuite/"*.png
diff --git a/tests/pngstest b/tests/pngstest
index 9d1b776..35dc751 100755
--- a/tests/pngstest
+++ b/tests/pngstest
@@ -1,5 +1,5 @@
 #!/bin/sh
-#
+
 # Usage:
 #
 #  tests/pngstest gamma alpha
diff --git a/tests/pngstest-1.8 b/tests/pngstest-1.8
index b2ec21b..8bfa66c 100755
--- a/tests/pngstest-1.8
+++ b/tests/pngstest-1.8
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec "${srcdir}/tests/pngstest" 1.8 none
diff --git a/tests/pngstest-1.8-alpha b/tests/pngstest-1.8-alpha
index d00596d..c93ea5b 100755
--- a/tests/pngstest-1.8-alpha
+++ b/tests/pngstest-1.8-alpha
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec "${srcdir}/tests/pngstest" 1.8 alpha
diff --git a/tests/pngstest-large-stride b/tests/pngstest-large-stride
new file mode 100755
index 0000000..e7948cc
--- /dev/null
+++ b/tests/pngstest-large-stride
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Large-stride test:
+# Use stride_extra > 32767 to trigger row_bytes > 65535 for linear images.
+exec ./pngstest \
+     --stride-extra 33000 \
+     --tmpfile "large-stride-" \
+     --log "${srcdir}/contrib/testpngs/rgb-alpha-16-linear.png"
diff --git a/tests/pngstest-linear b/tests/pngstest-linear
index 4a5b3fe..367699b 100755
--- a/tests/pngstest-linear
+++ b/tests/pngstest-linear
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec "${srcdir}/tests/pngstest" linear none
diff --git a/tests/pngstest-linear-alpha b/tests/pngstest-linear-alpha
index 4114210..d85391f 100755
--- a/tests/pngstest-linear-alpha
+++ b/tests/pngstest-linear-alpha
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec "${srcdir}/tests/pngstest" linear alpha
diff --git a/tests/pngstest-negative-stride b/tests/pngstest-negative-stride
new file mode 100755
index 0000000..acb72fc
--- /dev/null
+++ b/tests/pngstest-negative-stride
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Negative-stride test:
+# Bottom-up layout through all read/write paths.
+exec ./pngstest \
+     --negative-stride \
+     --tmpfile "negative-stride-" \
+     --log \
+     "${srcdir}/contrib/testpngs/gray-1.png" \
+     "${srcdir}/contrib/testpngs/gray-16-linear.png" \
+     "${srcdir}/contrib/testpngs/rgb-8-linear.png" \
+     "${srcdir}/contrib/testpngs/rgb-16-linear.png" \
+     "${srcdir}/contrib/testpngs/palette-8-tRNS.png" \
+     "${srcdir}/contrib/testpngs/gray-alpha-8-linear.png" \
+     "${srcdir}/contrib/testpngs/gray-alpha-16-linear.png" \
+     "${srcdir}/contrib/testpngs/rgb-alpha-8-1.8.png" \
+     "${srcdir}/contrib/testpngs/rgb-alpha-8-sRGB.png" \
+     "${srcdir}/contrib/testpngs/rgb-alpha-16-linear.png"
diff --git a/tests/pngstest-negative-stride-extra b/tests/pngstest-negative-stride-extra
new file mode 100755
index 0000000..f3df230
--- /dev/null
+++ b/tests/pngstest-negative-stride-extra
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# Negative-stride-extra test:
+# Bottom-up layout with non-aligned padding.
+exec ./pngstest \
+     --negative-stride \
+     --stride-extra 7 \
+     --tmpfile "negative-stride-extra-" \
+     --log \
+     "${srcdir}/contrib/testpngs/gray-1.png" \
+     "${srcdir}/contrib/testpngs/gray-16-linear.png" \
+     "${srcdir}/contrib/testpngs/rgb-8-linear.png" \
+     "${srcdir}/contrib/testpngs/rgb-16-linear.png" \
+     "${srcdir}/contrib/testpngs/palette-8-tRNS.png" \
+     "${srcdir}/contrib/testpngs/gray-alpha-8-linear.png" \
+     "${srcdir}/contrib/testpngs/gray-alpha-16-linear.png" \
+     "${srcdir}/contrib/testpngs/rgb-alpha-8-1.8.png" \
+     "${srcdir}/contrib/testpngs/rgb-alpha-8-sRGB.png" \
+     "${srcdir}/contrib/testpngs/rgb-alpha-16-linear.png"
diff --git a/tests/pngstest-none b/tests/pngstest-none
index 09ad740..3ff1e16 100755
--- a/tests/pngstest-none
+++ b/tests/pngstest-none
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec "${srcdir}/tests/pngstest" none none
diff --git a/tests/pngstest-none-alpha b/tests/pngstest-none-alpha
index af6083f..4d424e7 100755
--- a/tests/pngstest-none-alpha
+++ b/tests/pngstest-none-alpha
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec "${srcdir}/tests/pngstest" none alpha
diff --git a/tests/pngstest-sRGB b/tests/pngstest-sRGB
index d846081..a94e7cf 100755
--- a/tests/pngstest-sRGB
+++ b/tests/pngstest-sRGB
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec "${srcdir}/tests/pngstest" sRGB none
diff --git a/tests/pngstest-sRGB-alpha b/tests/pngstest-sRGB-alpha
index f1e57d3..411be49 100755
--- a/tests/pngstest-sRGB-alpha
+++ b/tests/pngstest-sRGB-alpha
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec "${srcdir}/tests/pngstest" sRGB alpha
diff --git a/tests/pngunknown-IDAT b/tests/pngunknown-IDAT
index 8e7281a..12dcb11 100755
--- a/tests/pngunknown-IDAT
+++ b/tests/pngunknown-IDAT
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngunknown --strict default=discard IDAT=save "${srcdir}/pngtest.png"
diff --git a/tests/pngunknown-discard b/tests/pngunknown-discard
index c96dab3..997df05 100755
--- a/tests/pngunknown-discard
+++ b/tests/pngunknown-discard
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngunknown --strict default=discard "${srcdir}/pngtest.png"
diff --git a/tests/pngunknown-if-safe b/tests/pngunknown-if-safe
index 31a4954..bbca391 100755
--- a/tests/pngunknown-if-safe
+++ b/tests/pngunknown-if-safe
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngunknown --strict default=if-safe "${srcdir}/pngtest.png"
diff --git a/tests/pngunknown-sAPI b/tests/pngunknown-sAPI
index 88a1f6b..8b9147f 100755
--- a/tests/pngunknown-sAPI
+++ b/tests/pngunknown-sAPI
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngunknown --strict bKGD=save cHRM=save gAMA=save all=discard iCCP=save sBIT=save sRGB=save eXIf=save "${srcdir}/pngtest.png"
diff --git a/tests/pngunknown-sTER b/tests/pngunknown-sTER
index 59c5ecd..9f45c70 100755
--- a/tests/pngunknown-sTER
+++ b/tests/pngunknown-sTER
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngunknown --strict sTER=if-safe "${srcdir}/pngtest.png"
diff --git a/tests/pngunknown-save b/tests/pngunknown-save
index 21412c4..7c42ef1 100755
--- a/tests/pngunknown-save
+++ b/tests/pngunknown-save
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngunknown --strict default=save "${srcdir}/pngtest.png"
diff --git a/tests/pngunknown-vpAg b/tests/pngunknown-vpAg
index 04f07b6..ff0f028 100755
--- a/tests/pngunknown-vpAg
+++ b/tests/pngunknown-vpAg
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngunknown --strict vpAg=if-safe "${srcdir}/pngtest.png"
diff --git a/tests/pngvalid-gamma-16-to-8 b/tests/pngvalid-gamma-16-to-8
index 074945d..8cab622 100755
--- a/tests/pngvalid-gamma-16-to-8
+++ b/tests/pngvalid-gamma-16-to-8
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-16-to-8
diff --git a/tests/pngvalid-gamma-alpha-mode b/tests/pngvalid-gamma-alpha-mode
index b16e8ab..78f74f4 100755
--- a/tests/pngvalid-gamma-alpha-mode
+++ b/tests/pngvalid-gamma-alpha-mode
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-alpha-mode
diff --git a/tests/pngvalid-gamma-background b/tests/pngvalid-gamma-background
index 3dbf58d..71ac9e8 100755
--- a/tests/pngvalid-gamma-background
+++ b/tests/pngvalid-gamma-background
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-background
diff --git a/tests/pngvalid-gamma-expand16-alpha-mode b/tests/pngvalid-gamma-expand16-alpha-mode
index 4fa10bd..4d65251 100755
--- a/tests/pngvalid-gamma-expand16-alpha-mode
+++ b/tests/pngvalid-gamma-expand16-alpha-mode
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-alpha-mode --expand16
diff --git a/tests/pngvalid-gamma-expand16-background b/tests/pngvalid-gamma-expand16-background
index ad9a917..72721b6 100755
--- a/tests/pngvalid-gamma-expand16-background
+++ b/tests/pngvalid-gamma-expand16-background
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-background --expand16
diff --git a/tests/pngvalid-gamma-expand16-transform b/tests/pngvalid-gamma-expand16-transform
index 5fd1f28..8003eed 100755
--- a/tests/pngvalid-gamma-expand16-transform
+++ b/tests/pngvalid-gamma-expand16-transform
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-transform --expand16
diff --git a/tests/pngvalid-gamma-sbit b/tests/pngvalid-gamma-sbit
index 6ca08e9..f6cf353 100755
--- a/tests/pngvalid-gamma-sbit
+++ b/tests/pngvalid-gamma-sbit
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-sbit
diff --git a/tests/pngvalid-gamma-threshold b/tests/pngvalid-gamma-threshold
index 3fe4edf..fcf7a53 100755
--- a/tests/pngvalid-gamma-threshold
+++ b/tests/pngvalid-gamma-threshold
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-threshold
diff --git a/tests/pngvalid-gamma-transform b/tests/pngvalid-gamma-transform
index 3fa62c6..441e380 100755
--- a/tests/pngvalid-gamma-transform
+++ b/tests/pngvalid-gamma-transform
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --gamma-transform
diff --git a/tests/pngvalid-progressive-interlace-standard b/tests/pngvalid-progressive-interlace-standard
index a8cdf9e..81b7e51 100755
--- a/tests/pngvalid-progressive-interlace-standard
+++ b/tests/pngvalid-progressive-interlace-standard
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --standard --progressive-read --interlace
diff --git a/tests/pngvalid-progressive-size b/tests/pngvalid-progressive-size
index 868d6dc..9e44c6a 100755
--- a/tests/pngvalid-progressive-size
+++ b/tests/pngvalid-progressive-size
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --size --progressive-read
diff --git a/tests/pngvalid-progressive-standard b/tests/pngvalid-progressive-standard
index bf7e5c1..35a15a5 100755
--- a/tests/pngvalid-progressive-standard
+++ b/tests/pngvalid-progressive-standard
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --standard --progressive-read
diff --git a/tests/pngvalid-standard b/tests/pngvalid-standard
index a89b884..2d7f712 100755
--- a/tests/pngvalid-standard
+++ b/tests/pngvalid-standard
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --standard
diff --git a/tests/pngvalid-transform b/tests/pngvalid-transform
index c7536ae..067c515 100755
--- a/tests/pngvalid-transform
+++ b/tests/pngvalid-transform
@@ -1,2 +1,3 @@
 #!/bin/sh
+
 exec ./pngvalid --strict --transform