Skip to content

Commit 98eb255

Browse files
committed
Better ktx1 conversions
1 parent 70cdd61 commit 98eb255

4 files changed

Lines changed: 304 additions & 128 deletions

File tree

BCnEncTests/Api/DecodingTests.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ enum FileType
2323
const string testImageFolderRoot = "testImages";
2424
const string referenceFolder = "reference";
2525

26-
static string[] unsupportedFilter = new string[] { "astc", "eac", "etc1", "etc2", "bc4s", "bc5s", "basisu" };
26+
static string[] unsupportedFilter = new string[] { "astc", "eac", "etc1", "etc2", "bc4s", "bc5s", "basisu", "pvrtc1" };
2727

2828
private static DecoderOutputOptions defaultOptions = new DecoderOutputOptions()
2929
{
3030
AlphaHandling = DecoderAlphaHandling.KeepAsIs,
3131
InputColorSpace = InputColorSpaceAssumption.Auto,
32-
OutputColorSpace = OutputColorSpaceTarget.ProcessLinearPreserveColorSpace
32+
OutputColorSpace = OutputColorSpaceTarget.ProcessLinearPreserveColorSpace,
33+
RescaleSnormToUnorm = true
3334
};
3435

3536
[SkippableTheory]
@@ -137,11 +138,12 @@ private static float GetTolerance(CompressionFormat format, FileType type)
137138

138139
return (format, type) switch
139140
{
140-
(_, _) when format.IsRawPixelFormat() => minTolerance,
141+
(_, FileType.Dds) when format.IsRawPixelFormat() => minTolerance,
142+
(_, FileType.Ktx) when format.IsRawPixelFormat() => one,
141143
(CompressionFormat.Bc7, _) => minTolerance,
142144
(CompressionFormat.Bc7_sRGB, _) => minTolerance,
143-
(CompressionFormat.Bc6S, _) => minTolerance,
144-
(CompressionFormat.Bc6U, _) => minTolerance,
145+
(CompressionFormat.Bc6S, _) => one,
146+
(CompressionFormat.Bc6U, _) => one,
145147
(_, FileType.Dds) => one, // Dds has a small tolerance
146148
(_, FileType.Ktx) => three, // Ktx has a large tolerance due to different decoding methods
147149
};

BCnEncTests/testImages/GenerateTestEncodes.sh

Lines changed: 204 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
#!/usr/bin/env bash
22
# GenerateTestEncodes.sh
3-
# Generates test DDS and KTX texture files using cuttlefish, then decodes
4-
# reference PNGs using tacentview.
3+
# Generates test DDS, KTX, and KTX2 texture files using cuttlefish, then decodes
4+
# reference PNGs. KTX2 files are converted from KTX using ktx2ktx2. Reference
5+
# PNGs for KTX/KTX2 are extracted with ktx extract (KTX2 only); DDS reference
6+
# PNGs are decoded with tacentview.
57
# Replaces GenerateTestDds.ps1 and GenerateTestKtx.ps1.
68
#
79
# Notes:
810
# - Only DX10-header DDS files are generated; cuttlefish does not support DX9/legacy headers.
911
# - ETC/EAC/ASTC/PVRTC formats are KTX-only (not valid in DDS containers).
10-
# - tacentview is used for reference decoding; if not found the decode step is skipped.
1112

1213
set -euo pipefail
1314

1415
usage() {
1516
cat <<'EOF'
16-
GenerateTestEncodes.sh - Generate test DDS and KTX files for BCnEncoder.NET
17+
GenerateTestEncodes.sh - Generate test DDS, KTX, and KTX2 files for BCnEncoder.NET
1718
1819
USAGE:
1920
./GenerateTestEncodes.sh -i <input> [OPTIONS]
@@ -23,28 +24,37 @@ OPTIONS:
2324
-i, --input <path> Path to input image (required unless --no-encode)
2425
-d, --dds-dir <path> DDS directory (default: ./testdds)
2526
-k, --ktx-dir <path> KTX directory (default: ./testktx)
27+
-K, --ktx2-dir <path> KTX2 directory (default: ./testktx2)
2628
--no-encode Skip encoding; only decode reference PNGs from
27-
existing files in the DDS and KTX directories
28-
-h, --help Show this help
29+
existing files in the DDS, KTX, and KTX2 directories
30+
--tacentview-ktx Use tacentview for KTX reference decoding instead
31+
of the default ktx extract method
2932
3033
TOOLS:
3134
cuttlefish Required for encoding (not needed with --no-encode)
32-
tacentview Required for reference PNG decoding
35+
ktx2ktx2 Required for KTX -> KTX2 conversion
36+
ktx Required for KTX2 reference PNG extraction
37+
tacentview Required for DDS reference PNG decoding; also used for KTX
38+
decoding if --tacentview-ktx is set
3339
EOF
3440
}
3541

3642
INPUT=""
3743
DDS_DIR="./testdds"
3844
KTX_DIR="./testktx"
45+
KTX2_DIR="./testktx2"
3946
NO_ENCODE=0
47+
TACENTVIEW_KTX=0
4048

4149
while [[ $# -gt 0 ]]; do
4250
case "$1" in
43-
-i|--input) INPUT="$2"; shift 2 ;;
44-
-d|--dds-dir) DDS_DIR="$2"; shift 2 ;;
45-
-k|--ktx-dir) KTX_DIR="$2"; shift 2 ;;
46-
--no-encode) NO_ENCODE=1; shift ;;
47-
-h|--help) usage; exit 0 ;;
51+
-i|--input) INPUT="$2"; shift 2 ;;
52+
-d|--dds-dir) DDS_DIR="$2"; shift 2 ;;
53+
-k|--ktx-dir) KTX_DIR="$2"; shift 2 ;;
54+
-K|--ktx2-dir) KTX2_DIR="$2"; shift 2 ;;
55+
--no-encode) NO_ENCODE=1; shift ;;
56+
--tacentview-ktx) TACENTVIEW_KTX=1; shift ;;
57+
-h|--help) usage; exit 0 ;;
4858
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
4959
esac
5060
done
@@ -57,11 +67,15 @@ fi
5767

5868
HAS_TACENTVIEW=0
5969
command -v tacentview &>/dev/null && HAS_TACENTVIEW=1 \
60-
|| echo "Warning: tacentview not found — reference PNG decode step will be skipped"
70+
|| echo "Warning: tacentview not found — DDS reference PNG decoding will be skipped"
71+
72+
HAS_KTX_TOOLS=0
73+
command -v ktx2ktx2 &>/dev/null && command -v ktx &>/dev/null && HAS_KTX_TOOLS=1 \
74+
|| echo "Warning: ktx2ktx2/ktx not found — KTX2 conversion and reference PNG extraction will be skipped"
6175

6276
if [[ "$NO_ENCODE" == "0" ]]; then
6377
BASE_NAME="$(basename "${INPUT%.*}")"
64-
mkdir -p "$DDS_DIR" "$KTX_DIR"
78+
mkdir -p "$DDS_DIR" "$KTX_DIR" "$KTX2_DIR"
6579
fi
6680

6781
run_cf() {
@@ -105,10 +119,10 @@ COMMON_FORMATS=(
105119
"bc3|BC3|unorm"
106120
# BC4
107121
"bc4|BC4|unorm"
108-
"bc4-snorm|BC4|snorm"
122+
"bc4s|BC4|snorm"
109123
# BC5
110124
"bc5|BC5|unorm"
111-
"bc5-snorm|BC5|snorm"
125+
"bc5s|BC5|snorm"
112126
# BC6H
113127
"bc6h|BC6H|ufloat"
114128
"bc6h-signed|BC6H|float"
@@ -127,9 +141,9 @@ COMMON_FORMATS=(
127141
"a1r5g5b5|A1R5G5B5|unorm"
128142
# Uncompressed 8-bit
129143
"r8|R8|unorm"
130-
"r8-snorm|R8|snorm"
144+
"r8s|R8|snorm"
131145
"r8g8|R8G8|unorm"
132-
"r8g8-snorm|R8G8|snorm"
146+
"r8g8s|R8G8|snorm"
133147
"r8g8b8|R8G8B8|unorm"
134148
"b8g8r8|B8G8R8|unorm"
135149
"r8g8b8a8|R8G8B8A8|unorm"
@@ -169,9 +183,9 @@ KTX_EXTRA_FORMATS=(
169183
"etc2-rgb-a1|ETC2_R8G8B8A1|unorm"
170184
# EAC (single- and dual-channel, signed and unsigned)
171185
"eac-r11|EAC_R11|unorm"
172-
"eac-r11-snorm|EAC_R11|snorm"
186+
"eac-r11s|EAC_R11|snorm"
173187
"eac-rg11|EAC_R11G11|unorm"
174-
"eac-rg11-snorm|EAC_R11G11|snorm"
188+
"eac-rg11s|EAC_R11G11|snorm"
175189
# ASTC
176190
"astc-4x4|ASTC_4x4|"
177191
"astc-5x4|ASTC_5x4|"
@@ -231,25 +245,58 @@ done
231245
echo "KTX generation complete. Files written to: $KTX_DIR"
232246
fi # NO_ENCODE
233247

234-
# ─── Reference PNG decode ──────────────────────────────────────────────────────
235-
# Decodes each generated DDS/KTX back to PNG for visual inspection.
236-
# tacentview outputs the PNG alongside the source file, which is then moved
237-
# into a reference/ subdirectory.
248+
# ─── Convert KTX -> KTX2 ─────────────────────────────────────────────────────
249+
# ktx extract (used for reference PNGs) only supports KTX2, so convert each
250+
# KTX file. The pixel data is identical; KTX2 is just a different container.
251+
252+
if [[ "$HAS_KTX_TOOLS" == "1" ]]; then
253+
mkdir -p "$KTX2_DIR"
254+
ktx_files=("$KTX_DIR"/*.ktx)
255+
if [[ -f "${ktx_files[0]}" ]]; then
256+
echo ""
257+
echo "Converting KTX -> KTX2..."
258+
total="${#ktx_files[@]}" count=0
259+
for ktx in "${ktx_files[@]}"; do
260+
count=$((count + 1))
261+
name="$(basename "${ktx%.*}")"
262+
ktx2="$KTX2_DIR/${name}.ktx2"
263+
printf '[%d/%d] %s\n' "$count" "$total" "$name"
264+
if [[ -f "$ktx2" ]]; then
265+
echo " Skipped (already exists): $ktx2"
266+
elif ktx2ktx2 -o "$ktx2" "$ktx"; then
267+
echo " Created $ktx2"
268+
else
269+
echo " Warning: ktx2ktx2 failed for $ktx" >&2
270+
fi
271+
done
272+
echo "KTX2 conversion complete. Files written to: $KTX2_DIR"
273+
else
274+
echo "No KTX files found in $KTX_DIR — skipping KTX2 conversion"
275+
fi
276+
else
277+
echo ""
278+
echo "Skipping KTX2 conversion (ktx2ktx2 not available)."
279+
fi
280+
281+
# ─── Reference PNG decode ─────────────────────────────────────────────────────
282+
# DDS: decoded with tacentview
283+
# KTX/KTX2: decoded with ktx extract (KTX2 only); PNGs are then copied from
284+
# the KTX2 reference folder into the KTX reference folder since the
285+
# pixel data is identical. Falls back to tacentview if --tacentview-ktx.
238286
#
239-
# HDR formats (BC6H, float, ufloat) get tone=1.0 (neutral exposure) so the
240-
# result is representable as an 8-bit PNG. corr=auto respects any sRGB tagging
241-
# embedded in the file by cuttlefish.
287+
# HDR formats (BC6H, float, ufloat) get tone=1.0 for tacentview so the preview
288+
# is representable as an 8-bit PNG.
242289

243-
decode_references() {
244-
local dir="$1" ext="$2" in_flag="$3"
290+
decode_dds_references() {
291+
local dir="$1"
245292
local ref_dir="$dir/reference"
246293
mkdir -p "$ref_dir"
247294

248-
local files=("$dir"/*."$ext")
249-
[[ -f "${files[0]}" ]] || { echo "No .$ext files found in $dir"; return; }
295+
local files=("$dir"/*.dds)
296+
[[ -f "${files[0]}" ]] || { echo "No .dds files found in $dir"; return; }
250297

251298
local total="${#files[@]}" count=0
252-
echo "Decoding $ext reference PNGs..."
299+
echo "Decoding DDS reference PNGs with tacentview..."
253300

254301
for src in "${files[@]}"; do
255302
count=$((count + 1))
@@ -259,20 +306,116 @@ decode_references() {
259306

260307
printf '[%d/%d] %s\n' "$count" "$total" "$name"
261308

262-
# Apply neutral tone-map for HDR/float formats so the preview is
263-
# representable as an 8-bit PNG
264-
local params="corr=auto"
265-
if [[ "$name" == *bc6h* ]] || [[ "$name" == *float* ]] || [[ "$name" == *ufloat* ]]; then
266-
params="corr=auto,tone=1.0"
309+
if [[ -f "$ref_png" ]]; then
310+
echo " Skipped (already exists): $ref_png"
311+
continue
312+
fi
313+
314+
local params="corr=none"
315+
316+
if tacentview -c -w --inDDS "$params" -o png "$src"; then
317+
if [[ -f "$out_png" ]]; then
318+
mv "$out_png" "$ref_png"
319+
echo " Created $ref_png"
320+
else
321+
echo " Warning: expected $out_png not found" >&2
322+
fi
323+
else
324+
echo " Warning: tacentview failed for $src" >&2
325+
fi
326+
done
327+
328+
echo "DDS reference PNGs written to: $ref_dir"
329+
}
330+
331+
decode_ktx2_references() {
332+
local ktx2_dir="$1" ktx_dir="$2"
333+
local ktx2_ref_dir="$ktx2_dir/reference"
334+
local ktx_ref_dir="$ktx_dir/reference"
335+
mkdir -p "$ktx2_ref_dir" "$ktx_ref_dir"
336+
337+
local files=("$ktx2_dir"/*.ktx2)
338+
[[ -f "${files[0]}" ]] || { echo "No .ktx2 files found in $ktx2_dir"; return; }
339+
340+
local total="${#files[@]}" count=0
341+
echo "Extracting KTX2 reference PNGs with ktx extract (falling back to tacentview for block-compressed)..."
342+
343+
for src in "${files[@]}"; do
344+
count=$((count + 1))
345+
local name; name="$(basename "${src%.*}")"
346+
local ktx_src="$ktx_dir/${name}.ktx"
347+
local ktx2_ref_png="$ktx2_ref_dir/${name}.png"
348+
local ktx_ref_png="$ktx_ref_dir/${name}.png"
349+
350+
printf '[%d/%d] %s\n' "$count" "$total" "$name"
351+
352+
if [[ ! -f "$ktx2_ref_png" ]]; then
353+
# ktx extract only decodes uncompressed KTX2 to PNG; block-compressed
354+
# KTX2 files (BCn, ETC, ASTC) are not "transcodable" without BasisLZ
355+
# supercompression, so extract won't produce a PNG for those. We detect
356+
# this by checking whether the output file was actually created.
357+
ktx extract "$src" "$ktx2_ref_png" 2>/dev/null || true
358+
if [[ -f "$ktx2_ref_png" ]]; then
359+
echo " Created $ktx2_ref_png"
360+
elif [[ "$HAS_TACENTVIEW" == "1" && -f "$ktx_src" ]]; then
361+
echo " ktx extract produced no PNG (block-compressed); falling back to tacentview"
362+
local out_png="${ktx_src%.*}.png"
363+
local params="corr=none"
364+
if tacentview -c -w --inKTX "$params" -o png "$ktx_src" && [[ -f "$out_png" ]]; then
365+
mv "$out_png" "$ktx2_ref_png"
366+
echo " Created $ktx2_ref_png"
367+
else
368+
echo " Warning: tacentview fallback also failed for $name" >&2
369+
continue
370+
fi
371+
else
372+
echo " Warning: no PNG produced for $name (block-compressed; tacentview unavailable)" >&2
373+
continue
374+
fi
375+
else
376+
echo " Skipped (already exists): $ktx2_ref_png"
377+
fi
378+
379+
# Copy to KTX reference folder — pixel data is identical
380+
if [[ -f "$ktx_ref_png" ]]; then
381+
echo " Skipped (already exists): $ktx_ref_png"
382+
else
383+
cp "$ktx2_ref_png" "$ktx_ref_png"
384+
echo " Copied to $ktx_ref_png"
267385
fi
386+
done
387+
388+
echo "KTX2 reference PNGs written to: $ktx2_ref_dir"
389+
echo "KTX reference PNGs written to: $ktx_ref_dir"
390+
}
391+
392+
decode_ktx_references_tacentview() {
393+
local dir="$1"
394+
local ref_dir="$dir/reference"
395+
mkdir -p "$ref_dir"
396+
397+
local files=("$dir"/*.ktx)
398+
[[ -f "${files[0]}" ]] || { echo "No .ktx files found in $dir"; return; }
399+
400+
local total="${#files[@]}" count=0
401+
echo "Decoding KTX reference PNGs with tacentview..."
402+
403+
for src in "${files[@]}"; do
404+
count=$((count + 1))
405+
local name; name="$(basename "${src%.*}")"
406+
local out_png="${src%.*}.png"
407+
local ref_png="$ref_dir/${name}.png"
408+
409+
printf '[%d/%d] %s\n' "$count" "$total" "$name"
268410

269411
if [[ -f "$ref_png" ]]; then
270412
echo " Skipped (already exists): $ref_png"
271413
continue
272414
fi
273415

274-
# tacentview writes output alongside the input file; we then move it
275-
if tacentview -c -w "$in_flag" "$params" -o png "$src"; then
416+
local params="corr=none"
417+
418+
if tacentview -c -w --inKTX "$params" -o png "$src"; then
276419
if [[ -f "$out_png" ]]; then
277420
mv "$out_png" "$ref_png"
278421
echo " Created $ref_png"
@@ -284,19 +427,33 @@ decode_references() {
284427
fi
285428
done
286429

287-
echo "$ext reference PNGs written to: $ref_dir"
430+
echo "KTX reference PNGs written to: $ref_dir"
288431
}
289432

433+
echo ""
290434
if [[ "$HAS_TACENTVIEW" == "1" ]]; then
291-
echo ""
292-
[[ -d "$DDS_DIR" ]] && decode_references "$DDS_DIR" "dds" "--inDDS" \
435+
[[ -d "$DDS_DIR" ]] && decode_dds_references "$DDS_DIR" \
293436
|| echo "Skipping DDS decode: $DDS_DIR does not exist"
294-
echo ""
295-
[[ -d "$KTX_DIR" ]] && decode_references "$KTX_DIR" "ktx" "--inKTX" \
296-
|| echo "Skipping KTX decode: $KTX_DIR does not exist"
297437
else
298-
echo ""
299-
echo "Skipping reference PNG decode (tacentview not available)."
438+
echo "Skipping DDS reference PNG decode (tacentview not available)."
439+
fi
440+
441+
echo ""
442+
if [[ "$TACENTVIEW_KTX" == "1" ]]; then
443+
if [[ "$HAS_TACENTVIEW" == "1" ]]; then
444+
[[ -d "$KTX_DIR" ]] && decode_ktx_references_tacentview "$KTX_DIR" \
445+
|| echo "Skipping KTX decode: $KTX_DIR does not exist"
446+
else
447+
echo "Skipping KTX reference PNG decode (tacentview not available)."
448+
fi
449+
elif [[ "$HAS_KTX_TOOLS" == "1" ]]; then
450+
if [[ -d "$KTX2_DIR" ]]; then
451+
decode_ktx2_references "$KTX2_DIR" "$KTX_DIR"
452+
else
453+
echo "Skipping KTX/KTX2 decode: $KTX2_DIR does not exist"
454+
fi
455+
else
456+
echo "Skipping KTX reference PNG decode (ktx2ktx2/ktx not available; use --tacentview-ktx to use tacentview instead)."
300457
fi
301458

302459
echo ""

0 commit comments

Comments
 (0)