raw-webgpu 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +21 -0
- package/README.md +115 -0
- package/THIRD_PARTY_LICENSES.txt +2842 -0
- package/dist/decode/instantiate.d.ts +3 -0
- package/dist/decode/module.d.ts +2 -0
- package/dist/decode/session.d.ts +7 -0
- package/dist/decode/tiff-worker.d.ts +1 -0
- package/dist/decode/worker.d.ts +1 -0
- package/dist/develop/gpu.d.ts +27 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +710 -0
- package/dist/libraw.js +2 -0
- package/dist/libraw.wasm +0 -0
- package/dist/math.d.ts +4 -0
- package/dist/tiff/color.d.ts +17 -0
- package/dist/tiff/index.d.ts +7 -0
- package/dist/tiff/upload.d.ts +21 -0
- package/dist/tiff-worker.js +181 -0
- package/dist/types.d.ts +51 -0
- package/dist/worker.js +116 -0
- package/docs/conversion.md +52 -0
- package/native/bridge.cpp +119 -0
- package/native/build.py +229 -0
- package/native/calibration.cpp +136 -0
- package/native/direct.h +29 -0
- package/native/dng-no-xmp.patch +34 -0
- package/native/dng.cpp +42 -0
- package/native/pixels.cpp +213 -0
- package/native/raw.h +47 -0
- package/native/tiff.cpp +164 -0
- package/package.json +68 -0
package/native/build.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Compile LibRaw and DNG SDK to browser WASM with Emscripten 6.0.9."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import tarfile
|
|
9
|
+
import urllib.request
|
|
10
|
+
import zipfile
|
|
11
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
NATIVE = Path(__file__).resolve().parent
|
|
15
|
+
PROJECT = NATIVE.parent
|
|
16
|
+
CACHE = Path(os.environ.get("RAW_WEBGPU_CACHE", PROJECT / ".cache/native")).resolve()
|
|
17
|
+
OUTPUT = PROJECT / "dist/libraw.js"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run(*args):
|
|
21
|
+
subprocess.run([str(arg) for arg in args], check=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def download(name, url, checksum):
|
|
25
|
+
archive = CACHE / name
|
|
26
|
+
if not archive.exists():
|
|
27
|
+
urllib.request.urlretrieve(url, archive)
|
|
28
|
+
if hashlib.sha256(archive.read_bytes()).hexdigest() != checksum:
|
|
29
|
+
raise RuntimeError(f"Source checksum mismatch: {name}")
|
|
30
|
+
return archive
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def prepare_sources(root):
|
|
34
|
+
raw_archive = download(
|
|
35
|
+
"libraw.tar.gz",
|
|
36
|
+
"https://www.libraw.org/data/LibRaw-0.22.2.tar.gz",
|
|
37
|
+
"de86b035655accff8d4010f1a221fdf50d353cb7b1422ba26f14a0db92612cfa",
|
|
38
|
+
)
|
|
39
|
+
sdk_archive = download(
|
|
40
|
+
"dng-sdk.zip",
|
|
41
|
+
"https://download.adobe.com/pub/adobe/dng/dng_sdk_1_7_1_2724_20260908.zip",
|
|
42
|
+
"740fbe95c69e09e9cd17654a5e4fef2d7021254b06fd2b8c5557b79a1496b50c",
|
|
43
|
+
)
|
|
44
|
+
raw = root / "LibRaw-0.22.2"
|
|
45
|
+
sdk = root / "dng_sdk_1_7_1"
|
|
46
|
+
if not raw.exists():
|
|
47
|
+
with tarfile.open(raw_archive) as archive:
|
|
48
|
+
archive.extractall(root, filter="data")
|
|
49
|
+
if not sdk.exists():
|
|
50
|
+
with zipfile.ZipFile(sdk_archive) as archive:
|
|
51
|
+
archive.extractall(root)
|
|
52
|
+
|
|
53
|
+
# SDK 1.7.1 omits its XMP feature guard in two JPEG XL metadata blocks.
|
|
54
|
+
source = sdk / "dng_sdk/source/dng_jxl.cpp"
|
|
55
|
+
if "#if qDNGUseXMP\n\n\t\tif (includeXMP" not in source.read_text():
|
|
56
|
+
run("patch", "-d", sdk, "-p1", "-i", NATIVE / "dng-no-xmp.patch")
|
|
57
|
+
return raw, sdk
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_jpeg_xl(em, source, output):
|
|
61
|
+
disabled = [
|
|
62
|
+
"TOOLS",
|
|
63
|
+
"JPEGLI",
|
|
64
|
+
"JPEGLI_LIBJPEG",
|
|
65
|
+
"DOXYGEN",
|
|
66
|
+
"MANPAGES",
|
|
67
|
+
"BENCHMARK",
|
|
68
|
+
"EXAMPLES",
|
|
69
|
+
"JNI",
|
|
70
|
+
"SJPEG",
|
|
71
|
+
"OPENEXR",
|
|
72
|
+
"TCMALLOC",
|
|
73
|
+
"WASM_THREADS",
|
|
74
|
+
"TRANSCODE_JPEG",
|
|
75
|
+
]
|
|
76
|
+
run(
|
|
77
|
+
em / "emcmake",
|
|
78
|
+
"cmake",
|
|
79
|
+
"-S",
|
|
80
|
+
source,
|
|
81
|
+
"-B",
|
|
82
|
+
output,
|
|
83
|
+
"-DBUILD_SHARED_LIBS=OFF",
|
|
84
|
+
"-DBUILD_TESTING=OFF",
|
|
85
|
+
"-DCMAKE_BUILD_TYPE=Release",
|
|
86
|
+
"-DJPEGXL_BUNDLE_LIBPNG=OFF",
|
|
87
|
+
"-DJPEGXL_ENABLE_SKCMS=ON",
|
|
88
|
+
*[f"-DJPEGXL_ENABLE_{name}=OFF" for name in disabled],
|
|
89
|
+
"-DCMAKE_CXX_FLAGS=-Oz -flto -fwasm-exceptions -msimd128",
|
|
90
|
+
"-DCMAKE_C_FLAGS=-Oz -flto -msimd128",
|
|
91
|
+
)
|
|
92
|
+
run(
|
|
93
|
+
"cmake",
|
|
94
|
+
"--build",
|
|
95
|
+
output,
|
|
96
|
+
"--parallel",
|
|
97
|
+
"6",
|
|
98
|
+
"--target",
|
|
99
|
+
"jxl_dec",
|
|
100
|
+
"jxl_threads",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def compiler_flags(includes):
|
|
105
|
+
return [
|
|
106
|
+
"-Oz",
|
|
107
|
+
"-flto",
|
|
108
|
+
"-fwasm-exceptions",
|
|
109
|
+
"-msimd128",
|
|
110
|
+
"-fno-rtti",
|
|
111
|
+
"-fvisibility=hidden",
|
|
112
|
+
"-fwhole-program-vtables",
|
|
113
|
+
"-fvirtual-function-elimination",
|
|
114
|
+
"-std=c++17",
|
|
115
|
+
"-w",
|
|
116
|
+
"-DLIBRAW_NOTHREADS",
|
|
117
|
+
"-DLIBRAW_NO_IOSTREAMS_DATASTREAM",
|
|
118
|
+
"-DUSE_DNGSDK",
|
|
119
|
+
"-DUSE_X3FTOOLS",
|
|
120
|
+
"-DUSE_ZLIB",
|
|
121
|
+
"--use-port=zlib",
|
|
122
|
+
"--use-port=libjpeg",
|
|
123
|
+
"-DqWeb=1",
|
|
124
|
+
"-DqDNGUseXMP=0",
|
|
125
|
+
"-DqDNGXMPFiles=0",
|
|
126
|
+
"-DqDNGXMPDocOps=0",
|
|
127
|
+
"-DqDNGThreadSafe=0",
|
|
128
|
+
"-DqDNGSupportVC5=0",
|
|
129
|
+
"-DqDNGUseLibJPEG=1",
|
|
130
|
+
"-DqDNG64Bit=0",
|
|
131
|
+
"-DqDNGLittleEndian=1",
|
|
132
|
+
"-DqDNGValidate=0",
|
|
133
|
+
] + [f"-I{path}" for path in includes]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def build_libraries(em, root, raw, sdk, flags):
|
|
137
|
+
manifest = (raw / "Makefile.am").read_text()
|
|
138
|
+
manifest = manifest.split("lib_libraw_a_SOURCES =", 1)[1]
|
|
139
|
+
manifest = manifest.split("lib_libraw_r_a_CXXFLAGS", 1)[0]
|
|
140
|
+
sources = [raw / path for path in re.findall(r"src/[\w/]+\.cpp", manifest)]
|
|
141
|
+
excluded = {"dng_validate", "dng_xmp", "dng_xmp_sdk", "dng_update_meta"}
|
|
142
|
+
sources.extend(path for path in sdk.glob("*.cpp") if path.stem not in excluded)
|
|
143
|
+
objects = root / "objects"
|
|
144
|
+
objects.mkdir(exist_ok=True)
|
|
145
|
+
|
|
146
|
+
def compile_source(source):
|
|
147
|
+
target = objects / f"{source.stem}.o"
|
|
148
|
+
if not target.exists():
|
|
149
|
+
run(em / "em++", *flags, "-c", source, "-o", target)
|
|
150
|
+
return target
|
|
151
|
+
|
|
152
|
+
with ThreadPoolExecutor(max_workers=6) as pool:
|
|
153
|
+
compiled = list(pool.map(compile_source, sources))
|
|
154
|
+
library = root / "libraries.a"
|
|
155
|
+
run(em / "emar", "rcs", library, *compiled)
|
|
156
|
+
return library
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def link_wasm(em, flags, library, jxl):
|
|
160
|
+
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
run(
|
|
162
|
+
em / "em++",
|
|
163
|
+
*flags,
|
|
164
|
+
*sorted(NATIVE.glob("*.cpp")),
|
|
165
|
+
library,
|
|
166
|
+
*jxl.rglob("*.a"),
|
|
167
|
+
"-o",
|
|
168
|
+
OUTPUT,
|
|
169
|
+
"-sMODULARIZE=1",
|
|
170
|
+
"-sEXPORT_ES6=1",
|
|
171
|
+
"-sENVIRONMENT=web,worker",
|
|
172
|
+
"-sINCOMING_MODULE_JS_API=instantiateWasm",
|
|
173
|
+
"-sALLOW_MEMORY_GROWTH=1",
|
|
174
|
+
"-sSTACK_SIZE=1048576",
|
|
175
|
+
"-sMAXIMUM_MEMORY=2147483648",
|
|
176
|
+
"-sFILESYSTEM=0",
|
|
177
|
+
"-sEXPORTED_FUNCTIONS=_malloc,_free",
|
|
178
|
+
"-sEXPORTED_RUNTIME_METHODS=HEAPU8,HEAPU16,HEAPU32,HEAPF64,UTF8ToString",
|
|
179
|
+
"-sASSERTIONS=0",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main():
|
|
184
|
+
compiler = shutil.which("em++")
|
|
185
|
+
if not compiler:
|
|
186
|
+
raise RuntimeError("Activate Emscripten 6.0.9 before running build")
|
|
187
|
+
version = subprocess.check_output([compiler, "--version"], text=True)
|
|
188
|
+
if "6.0.9" not in version:
|
|
189
|
+
raise RuntimeError("This build is pinned to Emscripten 6.0.9")
|
|
190
|
+
|
|
191
|
+
recipe = hashlib.sha256(
|
|
192
|
+
version.encode()
|
|
193
|
+
+ Path(__file__).read_bytes()
|
|
194
|
+
+ (NATIVE / "dng-no-xmp.patch").read_bytes()
|
|
195
|
+
).hexdigest()
|
|
196
|
+
root = CACHE / recipe[:16]
|
|
197
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
198
|
+
fingerprint = hashlib.sha256(
|
|
199
|
+
recipe.encode()
|
|
200
|
+
+ b"".join(
|
|
201
|
+
path.read_bytes()
|
|
202
|
+
for path in sorted(NATIVE.iterdir())
|
|
203
|
+
if path.suffix in {".cpp", ".h"}
|
|
204
|
+
)
|
|
205
|
+
).hexdigest()
|
|
206
|
+
stamp = root / "output.sha256"
|
|
207
|
+
if (
|
|
208
|
+
OUTPUT.exists()
|
|
209
|
+
and OUTPUT.with_suffix(".wasm").exists()
|
|
210
|
+
and stamp.exists()
|
|
211
|
+
and stamp.read_text() == fingerprint
|
|
212
|
+
):
|
|
213
|
+
print("Native build is up to date.")
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
em = Path(compiler).resolve().parent
|
|
217
|
+
raw, sdk_root = prepare_sources(root)
|
|
218
|
+
sdk = sdk_root / "dng_sdk/source"
|
|
219
|
+
jxl = sdk_root / "libjxl/libjxl"
|
|
220
|
+
jxl_build = root / "jxl-build"
|
|
221
|
+
build_jpeg_xl(em, jxl, jxl_build)
|
|
222
|
+
flags = compiler_flags([raw, sdk, jxl / "lib/include", jxl_build / "lib/include"])
|
|
223
|
+
library = build_libraries(em, root, raw, sdk, flags)
|
|
224
|
+
link_wasm(em, flags, library, jxl_build)
|
|
225
|
+
stamp.write_text(fingerprint)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
if __name__ == "__main__":
|
|
229
|
+
main()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#include "dng_camera_profile.h"
|
|
2
|
+
#include "dng_color_space.h"
|
|
3
|
+
#include "dng_color_spec.h"
|
|
4
|
+
#include "dng_host.h"
|
|
5
|
+
#include "dng_ifd.h"
|
|
6
|
+
#include "dng_info.h"
|
|
7
|
+
#include "dng_lens_correction.h"
|
|
8
|
+
#include "dng_negative.h"
|
|
9
|
+
#include "dng_opcode_list.h"
|
|
10
|
+
#include "dng_stream.h"
|
|
11
|
+
#include "dng_temperature.h"
|
|
12
|
+
#include "raw.h"
|
|
13
|
+
#include <cmath>
|
|
14
|
+
#include <libraw/libraw.h>
|
|
15
|
+
#include <memory>
|
|
16
|
+
#include <stdexcept>
|
|
17
|
+
#include <string>
|
|
18
|
+
|
|
19
|
+
// Reads the DNG vignette opcode into source->vignette: five polynomial coefficients, then the
|
|
20
|
+
// origin and per-axis scale that map pixel centers to the radius the polynomial expects.
|
|
21
|
+
static void read_vignette(Raw *source, const dng_opcode &opcode) {
|
|
22
|
+
const auto ¶ms = static_cast<const dng_opcode_FixVignetteRadial &>(opcode).Params();
|
|
23
|
+
for (int k = 0; k < 5; ++k) {
|
|
24
|
+
source->vignette[k] = params.fParams[k];
|
|
25
|
+
}
|
|
26
|
+
double cx = source->width * params.fCenter.h, cy = source->height * params.fCenter.v;
|
|
27
|
+
double aspect = source->negative->PixelAspectRatio();
|
|
28
|
+
double radius =
|
|
29
|
+
std::hypot(std::max(cx, source->width - cx), std::max(cy, source->height - cy) / aspect);
|
|
30
|
+
source->vignette[5] = -cx / radius;
|
|
31
|
+
source->vignette[6] = -cy / (aspect * radius);
|
|
32
|
+
source->vignette[7] = 1 / radius;
|
|
33
|
+
source->vignette[8] = 1 / (aspect * radius);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void prepare_calibration(Raw *source, void *bytes, unsigned length) {
|
|
37
|
+
auto &raw = source->decoder;
|
|
38
|
+
source->negative.Reset(source->host.Make_dng_negative());
|
|
39
|
+
|
|
40
|
+
if (source->info) {
|
|
41
|
+
// DNG carries its own camera profiles; also check the opcodes the GPU path cannot apply.
|
|
42
|
+
dng_stream stream(bytes, length);
|
|
43
|
+
auto &info = *source->info;
|
|
44
|
+
source->negative->Parse(source->host, stream, info);
|
|
45
|
+
source->negative->PostParse(source->host, stream, info);
|
|
46
|
+
|
|
47
|
+
const auto &ifd = *info.fIFD[info.fMainIndex];
|
|
48
|
+
source->opcodes[0] = ifd.fOpcodeList1Count;
|
|
49
|
+
source->opcodes[1] = ifd.fOpcodeList2Count;
|
|
50
|
+
source->opcodes[2] = ifd.fOpcodeList3Count;
|
|
51
|
+
const uint64 offsets[] = {ifd.fOpcodeList1Offset, ifd.fOpcodeList2Offset,
|
|
52
|
+
ifd.fOpcodeList3Offset};
|
|
53
|
+
bool hasVignette = false;
|
|
54
|
+
bool needsPreparation = ifd.fBlackLevelDeltaHCount || ifd.fBlackLevelDeltaVCount;
|
|
55
|
+
for (unsigned stage = 0; stage < 3; ++stage) {
|
|
56
|
+
if (!source->opcodes[stage]) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
dng_opcode_list list(stage + 1);
|
|
60
|
+
list.Parse(source->host, stream, source->opcodes[stage], offsets[stage]);
|
|
61
|
+
for (unsigned i = 0; i < list.Count(); ++i) {
|
|
62
|
+
const auto &opcode = list.Entry(i);
|
|
63
|
+
if (stage == 2 && opcode.OpcodeID() == dngOpcode_FixVignetteRadial &&
|
|
64
|
+
!hasVignette) {
|
|
65
|
+
read_vignette(source, opcode);
|
|
66
|
+
hasVignette = true;
|
|
67
|
+
} else if (!opcode.Optional()) {
|
|
68
|
+
needsPreparation = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (needsPreparation) {
|
|
73
|
+
prepare_dng_pixels(source, bytes, length);
|
|
74
|
+
}
|
|
75
|
+
} else if (raw.imgdata.idata.colors == 3) {
|
|
76
|
+
// Other formats: wrap LibRaw's camera matrix as a single D65 profile.
|
|
77
|
+
source->negative->SetColorChannels(3);
|
|
78
|
+
AutoPtr<dng_camera_profile> profile(new dng_camera_profile);
|
|
79
|
+
dng_matrix matrix(3, 3);
|
|
80
|
+
for (int row = 0; row < 3; ++row) {
|
|
81
|
+
for (int col = 0; col < 3; ++col) {
|
|
82
|
+
matrix[row][col] = raw.imgdata.color.cam_xyz[row][col];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
profile->SetColorMatrix1(matrix);
|
|
86
|
+
profile->SetCalibrationIlluminant1(21); // D65 calibration in LibRaw camera tables.
|
|
87
|
+
source->negative->AddProfile(profile);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The as-shot multipliers give the camera neutral, which the profile turns into chromaticity.
|
|
91
|
+
if (source->negative->ProfileCount()) {
|
|
92
|
+
source->color = std::make_unique<dng_color_spec>(*source->negative,
|
|
93
|
+
&source->negative->ProfileByIndex(0));
|
|
94
|
+
dng_vector neutral(source->color->Channels());
|
|
95
|
+
bool valid = true;
|
|
96
|
+
for (unsigned c = 0; c < neutral.Count(); ++c) {
|
|
97
|
+
float multiplier = raw.imgdata.color.cam_mul[c];
|
|
98
|
+
valid &= multiplier > 0 && std::isfinite(multiplier);
|
|
99
|
+
neutral[c] = multiplier > 0 ? 1.0 / multiplier : 1.0;
|
|
100
|
+
}
|
|
101
|
+
if (!valid) {
|
|
102
|
+
// LibRaw supplies daylight multipliers when the file has no usable camera neutral.
|
|
103
|
+
source->daylightBalance = true;
|
|
104
|
+
for (unsigned c = 0; c < neutral.Count(); ++c) {
|
|
105
|
+
float multiplier = raw.imgdata.color.pre_mul[c];
|
|
106
|
+
if (!(multiplier > 0) || !std::isfinite(multiplier)) {
|
|
107
|
+
throw std::runtime_error("RAW file has no usable white balance calibration");
|
|
108
|
+
}
|
|
109
|
+
neutral[c] = 1.0 / multiplier;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
source->asShotXY = source->color->NeutralToXY(neutral);
|
|
113
|
+
source->asShot = dng_temperature(source->asShotXY);
|
|
114
|
+
}
|
|
115
|
+
if (!source->color || !(source->asShot.Temperature() > 0)) {
|
|
116
|
+
throw std::runtime_error("RAW camera calibration is not supported");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// A zero temperature selects the exact as-shot chromaticity.
|
|
121
|
+
double *calibrate(Raw *source, double temperature, double tint) {
|
|
122
|
+
source->color->SetWhiteXY(temperature > 0 ? dng_temperature(temperature, tint).Get_xy_coord()
|
|
123
|
+
: source->asShotXY);
|
|
124
|
+
const auto &white = source->color->CameraWhite();
|
|
125
|
+
auto matrix = dng_space_Rec2020_Linear::Get().MatrixFromPCS() * source->color->CameraToPCS();
|
|
126
|
+
|
|
127
|
+
// Gains neutralize the camera white; the matrix folds it back in, so gains then matrix equals
|
|
128
|
+
// the SDK's camera-to-Rec.2020 transform while still exposing the white balance.
|
|
129
|
+
for (int c = 0; c < 3; ++c) {
|
|
130
|
+
source->calibration[c] = 1.0 / white[c];
|
|
131
|
+
for (int r = 0; r < 3; ++r) {
|
|
132
|
+
source->calibration[3 + r * 3 + c] = matrix[r][c] * white[c];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return source->calibration;
|
|
136
|
+
}
|
package/native/direct.h
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "dng_ifd.h"
|
|
4
|
+
#include "dng_stream.h"
|
|
5
|
+
|
|
6
|
+
// Byte offset of the image data when it can be used straight from the file: one uncompressed
|
|
7
|
+
// strip, native-sized samples, no predictor. Zero otherwise.
|
|
8
|
+
inline uint32 uncompressed_offset(const dng_ifd &ifd, dng_stream &stream) {
|
|
9
|
+
if (ifd.fCompression != 1 || ifd.fPredictor != 1 || ifd.fPlanarConfiguration != 1 ||
|
|
10
|
+
ifd.fFillOrder != 1 || !ifd.fUsesStrips || ifd.fTileOffsetsCount != 1 ||
|
|
11
|
+
ifd.fTileByteCountsCount != 1 ||
|
|
12
|
+
(ifd.fBitsPerSample[0] != 8 && ifd.fBitsPerSample[0] != 16 &&
|
|
13
|
+
ifd.fBitsPerSample[0] != 32)) {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
stream.SetReadPosition(ifd.fTileOffsetsOffset);
|
|
18
|
+
const auto offset = stream.TagValue_uint64(ifd.fTileOffsetsType);
|
|
19
|
+
stream.SetReadPosition(ifd.fTileByteCountsOffset);
|
|
20
|
+
const auto count = stream.TagValue_uint64(ifd.fTileByteCountsType);
|
|
21
|
+
|
|
22
|
+
const uint64 bytes = uint64(ifd.fImageWidth) * ifd.fImageLength * ifd.fSamplesPerPixel *
|
|
23
|
+
(ifd.fBitsPerSample[0] / 8);
|
|
24
|
+
if (offset > stream.Length() || bytes > stream.Length() - offset || count < bytes ||
|
|
25
|
+
offset > UINT32_MAX) {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
return uint32(offset);
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
--- a/dng_sdk/source/dng_jxl.cpp
|
|
2
|
+
+++ b/dng_sdk/source/dng_jxl.cpp
|
|
3
|
+
@@ -1536,6 +1536,7 @@
|
|
4
|
+
} // exif
|
|
5
|
+
|
|
6
|
+
// XMP.
|
|
7
|
+
+#if qDNGUseXMP
|
|
8
|
+
|
|
9
|
+
if (includeXMP && metadata && metadata->GetXMP ())
|
|
10
|
+
{
|
|
11
|
+
@@ -1574,6 +1575,7 @@
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
} // xmp
|
|
15
|
+
+#endif
|
|
16
|
+
|
|
17
|
+
// IPTC.
|
|
18
|
+
|
|
19
|
+
@@ -3809,6 +3811,7 @@
|
|
20
|
+
const std::vector<uint8> &data)
|
|
21
|
+
{
|
|
22
|
+
|
|
23
|
+
+ #if qDNGUseXMP
|
|
24
|
+
#if qDNGValidate
|
|
25
|
+
|
|
26
|
+
if (gVerbose)
|
|
27
|
+
@@ -3874,6 +3877,7 @@
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
+#endif
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/*****************************************************************************/
|
package/native/dng.cpp
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// The SDK owns mandatory DNG corrections and their ordering. This compatibility path starts
|
|
2
|
+
// from the original file so LibRaw's linearization and black subtraction cannot run twice.
|
|
3
|
+
#include "dng_image.h"
|
|
4
|
+
#include "dng_pixel_buffer.h"
|
|
5
|
+
#include "dng_tag_types.h"
|
|
6
|
+
#include "raw.h"
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <stdexcept>
|
|
9
|
+
|
|
10
|
+
void prepare_dng_pixels(Raw *source, void *bytes, unsigned length) {
|
|
11
|
+
dng_stream stream(bytes, length);
|
|
12
|
+
auto &negative = *source->negative;
|
|
13
|
+
negative.ReadStage1Image(source->host, stream, *source->info);
|
|
14
|
+
negative.BuildStage2Image(source->host);
|
|
15
|
+
negative.BuildStage3Image(source->host);
|
|
16
|
+
const auto &image = *negative.Stage3Image();
|
|
17
|
+
if (image.Planes() != 3) {
|
|
18
|
+
throw std::runtime_error("DNG preparation did not produce three-channel camera RGB");
|
|
19
|
+
}
|
|
20
|
+
source->width = image.Bounds().W();
|
|
21
|
+
source->height = image.Bounds().H();
|
|
22
|
+
source->prepared.resize(size_t(source->width) * source->height * 4);
|
|
23
|
+
dng_pixel_buffer buffer;
|
|
24
|
+
buffer.fArea = image.Bounds();
|
|
25
|
+
buffer.fPlane = 0;
|
|
26
|
+
buffer.fPlanes = 3;
|
|
27
|
+
buffer.fRowStep = source->width * 4;
|
|
28
|
+
buffer.fColStep = 4;
|
|
29
|
+
buffer.fPlaneStep = 1;
|
|
30
|
+
buffer.fPixelType = ttFloat;
|
|
31
|
+
buffer.fPixelSize = sizeof(float);
|
|
32
|
+
buffer.fData = source->prepared.data();
|
|
33
|
+
image.Get(buffer);
|
|
34
|
+
AutoPtr<dng_image> empty;
|
|
35
|
+
negative.SetStage3Image(empty); // Keep calibration, release the SDK pixel allocation.
|
|
36
|
+
source->cpuPrepared = true;
|
|
37
|
+
source->mosaic = 0;
|
|
38
|
+
source->originalOffset = 0;
|
|
39
|
+
source->white = 1;
|
|
40
|
+
std::fill_n(source->black, 4, 0);
|
|
41
|
+
std::fill_n(source->vignette, 9, 0); // Stage 3 already applied the opcode list.
|
|
42
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#include "direct.h"
|
|
2
|
+
#include "dng_info.h"
|
|
3
|
+
#include "raw.h"
|
|
4
|
+
#include <memory>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
|
|
7
|
+
static void check(int code) {
|
|
8
|
+
if (code) {
|
|
9
|
+
throw std::runtime_error(libraw_strerror(code));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static void parse_dng(Raw *source, void *bytes, unsigned length) {
|
|
14
|
+
dng_stream stream(bytes, length);
|
|
15
|
+
auto info = std::make_unique<dng_info>();
|
|
16
|
+
info->Parse(source->host, stream);
|
|
17
|
+
info->PostParse(source->host);
|
|
18
|
+
if (!info->IsValidDNG()) {
|
|
19
|
+
throw std::runtime_error("Unsupported DNG metadata");
|
|
20
|
+
}
|
|
21
|
+
source->info = std::move(info);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Byte offset of a DNG mosaic the GPU can read as-is: unsigned 16-bit little-endian Bayer in one
|
|
25
|
+
// uncompressed strip, uncropped, with no linearization table or stage-1/2 opcodes. Zero otherwise.
|
|
26
|
+
static unsigned direct_bayer(Raw *source, void *bytes, unsigned length) {
|
|
27
|
+
const auto &data = source->decoder.imgdata;
|
|
28
|
+
if (!source->info || source->info->fBigEndian || data.idata.filters <= 1000 ||
|
|
29
|
+
data.sizes.top_margin || data.sizes.left_margin) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const auto &ifd = *source->info->fIFD[source->info->fMainIndex];
|
|
34
|
+
if (ifd.fBitsPerSample[0] != 16 || ifd.fSamplesPerPixel != 1 || ifd.fSampleFormat[0] != 1 ||
|
|
35
|
+
ifd.fPhotometricInterpretation != 32803 || ifd.fLinearizationTableCount ||
|
|
36
|
+
ifd.fOpcodeList1Count || ifd.fOpcodeList2Count || ifd.fImageWidth != data.sizes.width ||
|
|
37
|
+
ifd.fImageLength != data.sizes.height ||
|
|
38
|
+
ifd.fActiveArea != dng_rect(ifd.fImageLength, ifd.fImageWidth)) {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// A Uint16Array view over the file needs an even offset.
|
|
43
|
+
dng_stream stream(bytes, length);
|
|
44
|
+
const auto offset = uncompressed_offset(ifd, stream);
|
|
45
|
+
return offset % 2 ? 0 : offset;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Failure here selects LibRaw preparation; malformed files still report decoder errors.
|
|
49
|
+
struct GpuLayoutUnsupported : std::runtime_error {
|
|
50
|
+
using std::runtime_error::runtime_error;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
static void describe_pixels(Raw *source) {
|
|
54
|
+
auto &raw = source->decoder;
|
|
55
|
+
auto &idata = raw.imgdata.idata;
|
|
56
|
+
auto &levels = raw.imgdata.color;
|
|
57
|
+
auto &sizes = raw.imgdata.sizes;
|
|
58
|
+
if ((!source->cpuPrepared && idata.filters && idata.filters != 9 && idata.filters < 1000) ||
|
|
59
|
+
(!source->cpuPrepared && (sizes.iwidth != sizes.width || sizes.iheight != sizes.height))) {
|
|
60
|
+
throw GpuLayoutUnsupported("This RAW mosaic is not supported by the GPU pipeline");
|
|
61
|
+
}
|
|
62
|
+
if (source->cpuPrepared) {
|
|
63
|
+
idata.filters = 0; // Demosaiced samples are camera RGB from here on.
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
source->cfaSize = idata.filters == 9 ? 6 : 2;
|
|
67
|
+
|
|
68
|
+
// LibRaw stores a spatial black pattern of cblack[4] rows by cblack[5] columns from cblack[6].
|
|
69
|
+
// A pattern must fold into one black level per CFA color, or a single value for RGB.
|
|
70
|
+
unsigned blackRows = levels.cblack[4], blackCols = levels.cblack[5];
|
|
71
|
+
if ((blackRows && blackCols) &&
|
|
72
|
+
((idata.filters && (blackRows > source->cfaSize || blackCols > source->cfaSize)) ||
|
|
73
|
+
(!idata.filters && (blackRows != 1 || blackCols != 1)))) {
|
|
74
|
+
throw GpuLayoutUnsupported(
|
|
75
|
+
"This RAW black calibration is not supported by the GPU pipeline");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
source->width = sizes.width;
|
|
79
|
+
source->height = sizes.height;
|
|
80
|
+
if (sizes.width < 3 || sizes.height < 3) {
|
|
81
|
+
throw GpuLayoutUnsupported("RAW image is too small for the GPU pipeline");
|
|
82
|
+
}
|
|
83
|
+
source->flip = sizes.flip;
|
|
84
|
+
source->mosaic = idata.filters ? 1 : 0;
|
|
85
|
+
|
|
86
|
+
bool seen[4] = {};
|
|
87
|
+
unsigned counts[3] = {};
|
|
88
|
+
unsigned cells = source->mosaic ? source->cfaSize * source->cfaSize : 4;
|
|
89
|
+
for (unsigned p = 0; p < cells; ++p) {
|
|
90
|
+
unsigned y = p / source->cfaSize, x = p % source->cfaSize;
|
|
91
|
+
unsigned channel = source->mosaic ? raw.COLOR(y, x) : p;
|
|
92
|
+
source->cfa[p] = channel;
|
|
93
|
+
double black = levels.black + levels.cblack[channel];
|
|
94
|
+
if (blackRows && blackCols) {
|
|
95
|
+
black += levels.cblack[6 + (y % blackRows) * blackCols + x % blackCols];
|
|
96
|
+
}
|
|
97
|
+
if (seen[channel] && source->black[channel] != black) {
|
|
98
|
+
throw GpuLayoutUnsupported("Spatial RAW black calibration requires CPU preparation");
|
|
99
|
+
}
|
|
100
|
+
seen[channel] = true;
|
|
101
|
+
source->black[channel] = black;
|
|
102
|
+
if (black >= levels.maximum) {
|
|
103
|
+
throw std::runtime_error("Invalid RAW black and white calibration");
|
|
104
|
+
}
|
|
105
|
+
if (source->mosaic) {
|
|
106
|
+
counts[channel == 3 ? 1 : channel]++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (source->mosaic) {
|
|
110
|
+
if (!counts[0] || !counts[1] || !counts[2]) {
|
|
111
|
+
throw GpuLayoutUnsupported("The mosaic must contain red, green and blue samples");
|
|
112
|
+
}
|
|
113
|
+
// The 2x2 fast shader uses MHC's diagonal Bayer filters.
|
|
114
|
+
if (source->cfaSize == 2 && (counts[0] != 1 || counts[1] != 2 || counts[2] != 1 ||
|
|
115
|
+
((source->cfa[0] == 1 || source->cfa[0] == 3) !=
|
|
116
|
+
(source->cfa[3] == 1 || source->cfa[3] == 3)))) {
|
|
117
|
+
throw GpuLayoutUnsupported("Non-Bayer 2x2 mosaic requires CPU preparation");
|
|
118
|
+
}
|
|
119
|
+
for (unsigned y = 0; y < 12; ++y) {
|
|
120
|
+
for (unsigned x = 0; x < source->cfaSize; ++x) {
|
|
121
|
+
if (raw.COLOR(y, x) != source->cfa[(y % source->cfaSize) * source->cfaSize + x]) {
|
|
122
|
+
throw GpuLayoutUnsupported("Non-periodic mosaic requires CPU preparation");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
source->white = levels.maximum;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static void prepare_camera_rgb(Raw *source) {
|
|
131
|
+
auto &raw = source->decoder;
|
|
132
|
+
if (source->originalOffset) {
|
|
133
|
+
check(raw.unpack());
|
|
134
|
+
}
|
|
135
|
+
auto &options = raw.imgdata.params;
|
|
136
|
+
options.no_auto_scale = 1;
|
|
137
|
+
options.no_auto_bright = 1;
|
|
138
|
+
options.adjust_maximum_thr = 0;
|
|
139
|
+
options.output_color = 0;
|
|
140
|
+
options.gamm[0] = options.gamm[1] = 1;
|
|
141
|
+
options.use_fuji_rotate = 1;
|
|
142
|
+
check(raw.dcraw_process());
|
|
143
|
+
source->cpuPrepared = true;
|
|
144
|
+
source->originalOffset = 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
void prepare_pixels(Raw *source, void *bytes, unsigned length) {
|
|
148
|
+
auto &raw = source->decoder;
|
|
149
|
+
check(raw.open_buffer(bytes, length));
|
|
150
|
+
source->sensorMosaic = raw.imgdata.idata.filters != 0;
|
|
151
|
+
raw.imgdata.rawparams.options &= ~LIBRAW_RAWOPTIONS_CONVERTFLOAT_TO_INT;
|
|
152
|
+
if (raw.imgdata.idata.dng_version) {
|
|
153
|
+
parse_dng(source, bytes, length);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// A mosaic usable straight from the file skips LibRaw's unpacking.
|
|
157
|
+
source->originalOffset = direct_bayer(source, bytes, length);
|
|
158
|
+
if (!source->originalOffset) {
|
|
159
|
+
check(raw.unpack());
|
|
160
|
+
}
|
|
161
|
+
if (raw.imgdata.idata.colors != 3 || raw.imgdata.color.as_shot_wb_applied ||
|
|
162
|
+
raw.is_floating_point()) {
|
|
163
|
+
throw std::runtime_error("This RAW sensor layout is not supported by the GPU pipeline");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Choose how the samples reach the GPU. LibRaw's filters value is 9 for X-Trans, above 1000
|
|
167
|
+
// for a 2x2 Bayer pattern and zero for full-color images.
|
|
168
|
+
source->cpuPrepared = raw.is_fuji_rotated() || raw.imgdata.idata.is_foveon || raw.is_sraw() ||
|
|
169
|
+
raw.is_nikon_sraw();
|
|
170
|
+
if (source->originalOffset) {
|
|
171
|
+
raw.imgdata.sizes.iwidth = raw.imgdata.sizes.width;
|
|
172
|
+
raw.imgdata.sizes.iheight = raw.imgdata.sizes.height;
|
|
173
|
+
} else if (source->cpuPrepared) {
|
|
174
|
+
prepare_camera_rgb(source);
|
|
175
|
+
} else if (raw.imgdata.idata.dng_version && raw.imgdata.idata.filters > 1000 &&
|
|
176
|
+
raw.imgdata.rawdata.raw_image && raw.imgdata.sizes.top_margin == 0 &&
|
|
177
|
+
raw.imgdata.sizes.left_margin == 0 &&
|
|
178
|
+
raw.imgdata.sizes.raw_pitch == raw.imgdata.sizes.width * 2 &&
|
|
179
|
+
raw.imgdata.sizes.raw_height == raw.imgdata.sizes.height) {
|
|
180
|
+
// Standard uncropped DNG can keep LibRaw's single-channel allocation.
|
|
181
|
+
raw.imgdata.sizes.iwidth = raw.imgdata.sizes.width;
|
|
182
|
+
raw.imgdata.sizes.iheight = raw.imgdata.sizes.height;
|
|
183
|
+
} else {
|
|
184
|
+
check(raw.raw2image_ex(0));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
describe_pixels(source);
|
|
189
|
+
} catch (const GpuLayoutUnsupported &) {
|
|
190
|
+
if (source->cpuPrepared) {
|
|
191
|
+
throw;
|
|
192
|
+
}
|
|
193
|
+
prepare_camera_rgb(source);
|
|
194
|
+
describe_pixels(source);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
void pack_pixels(Raw *source) {
|
|
199
|
+
auto &raw = source->decoder;
|
|
200
|
+
if (source->prepared.empty() && source->mosaic && raw.imgdata.image) {
|
|
201
|
+
// Preserve LibRaw's camera-specific preparation, then pack its sparse image in place.
|
|
202
|
+
// Every write lands at or before the sample it read, so a forward walk is safe.
|
|
203
|
+
auto *packed = reinterpret_cast<unsigned short *>(raw.imgdata.image);
|
|
204
|
+
for (unsigned y = 0; y < source->height; ++y) {
|
|
205
|
+
for (unsigned x = 0; x < source->width; ++x) {
|
|
206
|
+
unsigned i = y * source->width + x;
|
|
207
|
+
packed[i] =
|
|
208
|
+
raw.imgdata.image[i][source->cfa[(y % source->cfaSize) * source->cfaSize +
|
|
209
|
+
x % source->cfaSize]];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|