Added
Link Here
|
1 |
https://github.com/darktable-org/darktable/pull/10044 (475a8bf version) |
2 |
|
3 |
--- DefineOptions.cmake.orig 2022-02-09 10:21:58 UTC |
4 |
+++ DefineOptions.cmake |
5 |
@@ -18,6 +18,7 @@ option(USE_OPENJPEG "Enable JPEG 2000 support" ON) |
6 |
option(BINARY_PACKAGE_BUILD "Sets march optimization to generic" OFF) |
7 |
option(USE_XMLLINT "Run xmllint to test if darktableconfig.xml is valid" ON) |
8 |
option(USE_OPENJPEG "Enable JPEG 2000 support" ON) |
9 |
+option(USE_JXL "Enable JPEG XL export support" ON) |
10 |
option(USE_WEBP "Enable WebP export support" ON) |
11 |
option(USE_AVIF "Enable AVIF support" ON) |
12 |
option(USE_HEIF "Enable HEIF/HEIC support" ON) |
13 |
--- cmake/modules/FindJXL.cmake.orig 2022-08-31 08:07:07 UTC |
14 |
+++ cmake/modules/FindJXL.cmake |
15 |
@@ -0,0 +1,38 @@ |
16 |
+# Find libjxl |
17 |
+# Will define: |
18 |
+# - JXL_FOUND |
19 |
+# - JXL_INCLUDE_DIRS directory to include for libjxl headers |
20 |
+# - JXL_LIBRARIES libraries to link to |
21 |
+ |
22 |
+include(LibFindMacros) |
23 |
+ |
24 |
+# Use pkg-config to get hints about paths |
25 |
+# libfind_pkg_check_modules(JXL_PKGCONF libjxl) <- this isn't working? |
26 |
+pkg_check_modules(JXL_PKGCONF QUIET libjxl) |
27 |
+ |
28 |
+find_path(JXL_INCLUDE_DIR |
29 |
+ NAMES jxl/encode.h |
30 |
+ HINTS ${JXL_PKGCONF_INCLUDE_DIRS} |
31 |
+) |
32 |
+ |
33 |
+find_library(JXL_LIBRARY |
34 |
+ NAMES jxl |
35 |
+ HINTS ${JXL_PKGCONF_LIBRARY_DIRS} |
36 |
+) |
37 |
+ |
38 |
+find_library(JXL_THREADS_LIBRARY |
39 |
+ NAMES jxl_threads |
40 |
+ HINTS ${JXL_PKGCONF_LIBRARY_DIRS} |
41 |
+) |
42 |
+ |
43 |
+include(FindPackageHandleStandardArgs) |
44 |
+find_package_handle_standard_args(JXL DEFAULT_MSG JXL_LIBRARY JXL_INCLUDE_DIR) |
45 |
+ |
46 |
+if(JXL_PKGCONF_VERSION VERSION_LESS JXL_FIND_VERSION) |
47 |
+ set(JXL_FOUND false) |
48 |
+endif() |
49 |
+ |
50 |
+if(JXL_FOUND) |
51 |
+ set(JXL_LIBRARIES ${JXL_LIBRARY} ${JXL_THREADS_LIBRARY}) |
52 |
+ set(JXL_INCLUDE_DIRS ${JXL_INCLUDE_DIR}) |
53 |
+endif(JXL_FOUND) |
54 |
--- cmake/windows-macros.cmake.orig 2022-02-09 10:21:58 UTC |
55 |
+++ cmake/windows-macros.cmake |
56 |
@@ -157,6 +157,15 @@ if (WIN32 AND NOT BUILD_MSYS2_INSTALL) |
57 |
) |
58 |
list(APPEND CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS ${TMP_SYSTEM_RUNTIME_LIBS}) |
59 |
endif() |
60 |
+ |
61 |
+ if(JXL_FOUND) |
62 |
+ file(GLOB TMP_SYSTEM_RUNTIME_LIBS |
63 |
+ #LIBJXL |
64 |
+ ${MINGW_PATH}/libjxl.dll |
65 |
+ ${MINGW_PATH}/libjxl_threads.dll |
66 |
+ ) |
67 |
+ list(APPEND CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS ${TMP_SYSTEM_RUNTIME_LIBS}) |
68 |
+ endif() |
69 |
|
70 |
if(WebP_FOUND) |
71 |
file(GLOB TMP_SYSTEM_RUNTIME_LIBS |
72 |
--- data/darktableconfig.xml.in.orig 2022-02-09 10:21:58 UTC |
73 |
+++ data/darktableconfig.xml.in |
74 |
@@ -2407,6 +2407,41 @@ |
75 |
<longdescription/> |
76 |
</dtconfig> |
77 |
<dtconfig> |
78 |
+ <name>plugins/imageio/format/jxl/bpp</name> |
79 |
+ <type min="0" max="5">int</type> |
80 |
+ <default>0</default> |
81 |
+ <shortdescription/> |
82 |
+ <longdescription/> |
83 |
+ </dtconfig> |
84 |
+ <dtconfig> |
85 |
+ <name>plugins/imageio/format/jxl/quality</name> |
86 |
+ <type min="0" max="100">int</type> |
87 |
+ <default>95</default> |
88 |
+ <shortdescription/> |
89 |
+ <longdescription/> |
90 |
+ </dtconfig> |
91 |
+ <dtconfig> |
92 |
+ <name>plugins/imageio/format/jxl/original</name> |
93 |
+ <type>bool</type> |
94 |
+ <default>true</default> |
95 |
+ <shortdescription/> |
96 |
+ <longdescription/> |
97 |
+ </dtconfig> |
98 |
+ <dtconfig> |
99 |
+ <name>plugins/imageio/format/jxl/effort</name> |
100 |
+ <type min="1" max="9">int</type> |
101 |
+ <default>7</default> |
102 |
+ <shortdescription/> |
103 |
+ <longdescription/> |
104 |
+ </dtconfig> |
105 |
+ <dtconfig> |
106 |
+ <name>plugins/imageio/format/jxl/tier</name> |
107 |
+ <type min="0" max="4">int</type> |
108 |
+ <default>0</default> |
109 |
+ <shortdescription/> |
110 |
+ <longdescription/> |
111 |
+ </dtconfig> |
112 |
+ <dtconfig> |
113 |
<name>plugins/imageio/format/webp/comp_type</name> |
114 |
<type min="0" max="1">int</type> |
115 |
<default>0</default> |
116 |
--- src/CMakeLists.txt.orig 2022-02-09 10:21:58 UTC |
117 |
+++ src/CMakeLists.txt |
118 |
@@ -340,6 +340,15 @@ endif(USE_OPENEXR) |
119 |
endif(OpenEXR_FOUND) |
120 |
endif(USE_OPENEXR) |
121 |
|
122 |
+if(USE_JXL) |
123 |
+ find_package(JXL 0.7.0) |
124 |
+ if(JXL_FOUND) |
125 |
+ include_directories(SYSTEM ${JXL_INCLUDE_DIRS}) |
126 |
+ list(APPEND LIBS ${JXL_LIBRARIES}) |
127 |
+ add_definitions(${JXL_DEFINITIONS}) |
128 |
+ endif(JXL_FOUND) |
129 |
+endif(USE_JXL) |
130 |
+ |
131 |
if(USE_WEBP) |
132 |
find_package(WebP 0.3.0) |
133 |
if(WebP_FOUND) |
134 |
--- src/imageio/format/CMakeLists.txt.orig 2022-02-09 10:21:58 UTC |
135 |
+++ src/imageio/format/CMakeLists.txt |
136 |
@@ -17,6 +17,16 @@ add_library(tiff MODULE "tiff.c") |
137 |
add_library(pfm MODULE "pfm.c") |
138 |
add_library(tiff MODULE "tiff.c") |
139 |
|
140 |
+if(JXL_FOUND) |
141 |
+ list(APPEND MODULES "jxl_format") |
142 |
+ add_library(jxl_format MODULE "jxl.c") |
143 |
+ # At least on Windows, have to link to libjxl explicitly here as symbols |
144 |
+ # from libdarktable get stripped (until read support is added). As a |
145 |
+ # consequence, have to also change the module name to not be libjxl. |
146 |
+ target_link_libraries(jxl_format PUBLIC ${JXL_LIBRARIES}) |
147 |
+ set_target_properties(jxl_format PROPERTIES OUTPUT_NAME jpegxl) |
148 |
+endif(JXL_FOUND) |
149 |
+ |
150 |
if(WebP_FOUND) |
151 |
list(APPEND MODULES "webp") |
152 |
add_library(webp MODULE "webp.c") |
153 |
--- src/imageio/format/jxl.c.orig 2022-08-31 08:07:07 UTC |
154 |
+++ src/imageio/format/jxl.c |
155 |
@@ -0,0 +1,668 @@ |
156 |
+/* |
157 |
+ This file is part of darktable, |
158 |
+ Copyright (C) 2021-2022 darktable developers. |
159 |
+ |
160 |
+ darktable is free software: you can redistribute it and/or modify |
161 |
+ it under the terms of the GNU General Public License as published by |
162 |
+ the Free Software Foundation, either version 3 of the License, or |
163 |
+ (at your option) any later version. |
164 |
+ |
165 |
+ darktable is distributed in the hope that it will be useful, |
166 |
+ but WITHOUT ANY WARRANTY; without even the implied warranty of |
167 |
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
168 |
+ GNU General Public License for more details. |
169 |
+ |
170 |
+ You should have received a copy of the GNU General Public License |
171 |
+ along with darktable. If not, see <http://www.gnu.org/licenses/>. |
172 |
+*/ |
173 |
+ |
174 |
+#include "bauhaus/bauhaus.h" |
175 |
+#include "common/colorspaces.h" |
176 |
+#include "common/darktable.h" |
177 |
+#include "common/exif.h" |
178 |
+#include "common/imageio.h" |
179 |
+#include "control/conf.h" |
180 |
+#include "imageio/format/imageio_format_api.h" |
181 |
+ |
182 |
+#include "jxl/encode.h" |
183 |
+#include "jxl/resizable_parallel_runner.h" |
184 |
+ |
185 |
+DT_MODULE(1) |
186 |
+ |
187 |
+typedef struct dt_imageio_jxl_t |
188 |
+{ |
189 |
+ dt_imageio_module_data_t global; |
190 |
+ int bpp; |
191 |
+ int quality; |
192 |
+ int original; |
193 |
+ int effort; |
194 |
+ int tier; |
195 |
+} dt_imageio_jxl_t; |
196 |
+ |
197 |
+typedef struct dt_imageio_jxl_gui_data_t |
198 |
+{ |
199 |
+ // Int (0:8b, 1:10b, 2:12b, 3:16b, 4:half, 5:float) |
200 |
+ GtkWidget *bpp; |
201 |
+ // Int (0-100): the quality of the image, roughly corresponding to JPEG quality (100 is lossless) |
202 |
+ GtkWidget *quality; |
203 |
+ // Bool: whether to encode using the original color profile or the internal XYB one |
204 |
+ GtkWidget *original; |
205 |
+ // Int (1-9): effort with which to encode output; higher is slower (default is 7) |
206 |
+ GtkWidget *effort; |
207 |
+ // Int (0-4): higher value favors decoding speed vs quality (default is 0) |
208 |
+ GtkWidget *tier; |
209 |
+} dt_imageio_jxl_gui_data_t; |
210 |
+ |
211 |
+ |
212 |
+void init(dt_imageio_module_format_t *self) |
213 |
+{ |
214 |
+#ifdef USE_LUA |
215 |
+ dt_lua_register_module_member(darktable.lua_state.state, self, dt_imageio_jxl_t, bpp, int); |
216 |
+ |
217 |
+ dt_lua_register_module_member(darktable.lua_state.state, self, dt_imageio_jxl_t, quality, int); |
218 |
+ |
219 |
+ dt_lua_register_module_member(darktable.lua_state.state, self, dt_imageio_jxl_t, original, int); |
220 |
+ |
221 |
+ dt_lua_register_module_member(darktable.lua_state.state, self, dt_imageio_jxl_t, effort, int); |
222 |
+ |
223 |
+ dt_lua_register_module_member(darktable.lua_state.state, self, dt_imageio_jxl_t, tier, int); |
224 |
+#endif |
225 |
+} |
226 |
+ |
227 |
+void cleanup(dt_imageio_module_format_t *self) |
228 |
+{ |
229 |
+} |
230 |
+ |
231 |
+ |
232 |
+const char *mime(dt_imageio_module_data_t *data) |
233 |
+{ |
234 |
+ return "image/jxl"; |
235 |
+} |
236 |
+ |
237 |
+const char *extension(dt_imageio_module_data_t *data) |
238 |
+{ |
239 |
+ return "jxl"; |
240 |
+} |
241 |
+ |
242 |
+int dimension(struct dt_imageio_module_format_t *self, struct dt_imageio_module_data_t *data, uint32_t *width, |
243 |
+ uint32_t *height) |
244 |
+{ |
245 |
+ // The maximum dimensions supported by jxl images |
246 |
+ *width = 1073741823U; |
247 |
+ *height = 1073741823U; |
248 |
+ return 1; |
249 |
+} |
250 |
+ |
251 |
+int bpp(dt_imageio_module_data_t *data) |
252 |
+{ |
253 |
+ return 32; /* always request float */ |
254 |
+} |
255 |
+ |
256 |
+ |
257 |
+int write_image(struct dt_imageio_module_data_t *data, const char *filename, const void *in_tmp, |
258 |
+ dt_colorspaces_color_profile_type_t over_type, const char *over_filename, void *exif, int exif_len, |
259 |
+ int imgid, int num, int total, struct dt_dev_pixelpipe_t *pipe, const gboolean export_masks) |
260 |
+{ |
261 |
+ // Return error code by default |
262 |
+ int ret = 1; |
263 |
+ |
264 |
+ float *pixels = NULL; |
265 |
+ uint8_t *out_buf = NULL; |
266 |
+ FILE *out_file = NULL; |
267 |
+ uint8_t *icc_buf = NULL; |
268 |
+ uint8_t *exif_buf = NULL; |
269 |
+ char *xmp_string = NULL; |
270 |
+ |
271 |
+#define LIBJXL_ASSERT(code) \ |
272 |
+ { \ |
273 |
+ if((JxlEncoderStatus)code != JXL_ENC_SUCCESS) \ |
274 |
+ { \ |
275 |
+ JxlEncoderError err = JxlEncoderGetError(encoder); \ |
276 |
+ dt_print(DT_DEBUG_IMAGEIO, "[jxl] libjxl call failed with err %d (src/imageio/format/jxl.c#L%d)\n", err, \ |
277 |
+ __LINE__); \ |
278 |
+ goto end; \ |
279 |
+ } \ |
280 |
+ } |
281 |
+ |
282 |
+#define JXL_FAIL(msg, ...) \ |
283 |
+ { \ |
284 |
+ dt_print(DT_DEBUG_IMAGEIO, "[jxl] " msg "\n", ##__VA_ARGS__); \ |
285 |
+ goto end; \ |
286 |
+ } |
287 |
+ |
288 |
+ const dt_imageio_jxl_t *params = (dt_imageio_jxl_t *)data; |
289 |
+ const uint32_t width = (uint32_t)params->global.width; |
290 |
+ const uint32_t height = (uint32_t)params->global.height; |
291 |
+ |
292 |
+ JxlEncoder *encoder = JxlEncoderCreate(NULL); |
293 |
+ |
294 |
+ const uint32_t num_threads = JxlResizableParallelRunnerSuggestThreads(width, height); |
295 |
+ void *runner = JxlResizableParallelRunnerCreate(NULL); |
296 |
+ if(!runner) JXL_FAIL("could not create resizable parallel runner"); |
297 |
+ JxlResizableParallelRunnerSetThreads(runner, num_threads); |
298 |
+ LIBJXL_ASSERT(JxlEncoderSetParallelRunner(encoder, JxlResizableParallelRunner, runner)); |
299 |
+ |
300 |
+ // Automatically freed when we destroy the encoder |
301 |
+ JxlEncoderFrameSettings *frame_settings = JxlEncoderFrameSettingsCreate(encoder, NULL); |
302 |
+ |
303 |
+ // Set encoder basic info |
304 |
+ JxlBasicInfo basic_info; |
305 |
+ JxlEncoderInitBasicInfo(&basic_info); |
306 |
+ basic_info.xsize = width; |
307 |
+ basic_info.ysize = height; |
308 |
+ switch(params->bpp) |
309 |
+ { |
310 |
+ case 0: |
311 |
+ basic_info.bits_per_sample = 8; |
312 |
+ basic_info.exponent_bits_per_sample = 0; |
313 |
+ break; |
314 |
+ case 1: |
315 |
+ basic_info.bits_per_sample = 10; |
316 |
+ basic_info.exponent_bits_per_sample = 0; |
317 |
+ break; |
318 |
+ case 2: |
319 |
+ basic_info.bits_per_sample = 12; |
320 |
+ basic_info.exponent_bits_per_sample = 0; |
321 |
+ break; |
322 |
+ case 3: |
323 |
+ basic_info.bits_per_sample = 16; |
324 |
+ basic_info.exponent_bits_per_sample = 0; |
325 |
+ break; |
326 |
+ case 4: |
327 |
+ basic_info.bits_per_sample = 16; |
328 |
+ basic_info.exponent_bits_per_sample = 5; |
329 |
+ break; |
330 |
+ default: |
331 |
+ basic_info.bits_per_sample = 32; |
332 |
+ basic_info.exponent_bits_per_sample = 8; |
333 |
+ break; |
334 |
+ } |
335 |
+ // Lossless only makes sense for integer modes |
336 |
+ if(basic_info.exponent_bits_per_sample == 0 && params->quality == 100) |
337 |
+ { |
338 |
+ // Must preserve original profile for lossless mode |
339 |
+ basic_info.uses_original_profile = JXL_TRUE; |
340 |
+ LIBJXL_ASSERT(JxlEncoderSetFrameDistance(frame_settings, 0.0f)); |
341 |
+ LIBJXL_ASSERT(JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE)); |
342 |
+ } |
343 |
+ else |
344 |
+ { |
345 |
+ basic_info.uses_original_profile = params->original == FALSE ? JXL_FALSE : JXL_TRUE; |
346 |
+ float distance = params->quality >= 30 ? 0.1f + (100 - params->quality) * 0.09f |
347 |
+ : 6.4f + powf(2.5f, (30 - params->quality) / 5.0f) / 6.25f; |
348 |
+ LIBJXL_ASSERT(JxlEncoderSetFrameDistance(frame_settings, distance)); |
349 |
+ } |
350 |
+ |
351 |
+ LIBJXL_ASSERT(JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, params->effort)); |
352 |
+ |
353 |
+ LIBJXL_ASSERT( |
354 |
+ JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, params->tier)); |
355 |
+ |
356 |
+ // Codestream level should be chosen automatically given the settings |
357 |
+ LIBJXL_ASSERT(JxlEncoderSetBasicInfo(encoder, &basic_info)); |
358 |
+ |
359 |
+ // Determine and set the encoder color space |
360 |
+ const dt_colorspaces_color_profile_t *output_profile |
361 |
+ = dt_colorspaces_get_output_profile(imgid, over_type, over_filename); |
362 |
+ const cmsHPROFILE out_profile = output_profile->profile; |
363 |
+ // Previous call will give us a more accurate color profile type |
364 |
+ // (not what the user requested in the export menu but what the image is actually using) |
365 |
+ over_type = output_profile->type; |
366 |
+ |
367 |
+ // If possible we want libjxl to save the color encoding in its own format, rather |
368 |
+ // than as an ICC binary blob which is possible. |
369 |
+ // If we are unable to find the required color encoding data for libjxl we will |
370 |
+ // just fallback to providing an ICC blob (and hope we can at least do that!). |
371 |
+ bool write_color_natively = true; |
372 |
+ |
373 |
+ JxlColorEncoding color_encoding; |
374 |
+ color_encoding.color_space = JXL_COLOR_SPACE_RGB; |
375 |
+ // If not explicitly set in the export menu, use the intent of the actual output profile |
376 |
+ if(pipe->icc_intent >= DT_INTENT_PERCEPTUAL && pipe->icc_intent < DT_INTENT_LAST) |
377 |
+ color_encoding.rendering_intent = (JxlRenderingIntent)pipe->icc_intent; |
378 |
+ else |
379 |
+ color_encoding.rendering_intent = (JxlRenderingIntent)cmsGetHeaderRenderingIntent(out_profile); |
380 |
+ |
381 |
+ // Attempt to find and set the known white point, primaries and transfer function. |
382 |
+ // If we can't find any of these we fall back to an ICC binary blob. |
383 |
+ switch(over_type) |
384 |
+ { |
385 |
+ case DT_COLORSPACE_SRGB: |
386 |
+ color_encoding.white_point = JXL_WHITE_POINT_D65; |
387 |
+ color_encoding.primaries = JXL_PRIMARIES_SRGB; |
388 |
+ color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; |
389 |
+ break; |
390 |
+ case DT_COLORSPACE_LIN_REC709: |
391 |
+ color_encoding.white_point = JXL_WHITE_POINT_D65; |
392 |
+ color_encoding.primaries = JXL_PRIMARIES_SRGB; |
393 |
+ color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; |
394 |
+ break; |
395 |
+ case DT_COLORSPACE_LIN_REC2020: |
396 |
+ color_encoding.white_point = JXL_WHITE_POINT_D65; |
397 |
+ color_encoding.primaries = JXL_PRIMARIES_2100; |
398 |
+ color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; |
399 |
+ break; |
400 |
+ // TODO: enable when JXL_PRIMARIES_XYZ are added to libjxl |
401 |
+ // case DT_COLORSPACE_XYZ: |
402 |
+ // color_encoding.white_point = JXL_WHITE_POINT_E; |
403 |
+ // color_encoding.primaries = JXL_PRIMARIES_XYZ; |
404 |
+ // color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; |
405 |
+ // break; |
406 |
+ case DT_COLORSPACE_REC709: |
407 |
+ color_encoding.white_point = JXL_WHITE_POINT_D65; |
408 |
+ color_encoding.primaries = JXL_PRIMARIES_SRGB; |
409 |
+ color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_709; |
410 |
+ break; |
411 |
+ case DT_COLORSPACE_PQ_REC2020: |
412 |
+ color_encoding.white_point = JXL_WHITE_POINT_D65; |
413 |
+ color_encoding.primaries = JXL_PRIMARIES_2100; |
414 |
+ color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_PQ; |
415 |
+ break; |
416 |
+ case DT_COLORSPACE_HLG_REC2020: |
417 |
+ color_encoding.white_point = JXL_WHITE_POINT_D65; |
418 |
+ color_encoding.primaries = JXL_PRIMARIES_2100; |
419 |
+ color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_HLG; |
420 |
+ break; |
421 |
+ case DT_COLORSPACE_PQ_P3: |
422 |
+ color_encoding.white_point = JXL_WHITE_POINT_D65; |
423 |
+ color_encoding.primaries = JXL_PRIMARIES_P3; |
424 |
+ color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_PQ; |
425 |
+ break; |
426 |
+ case DT_COLORSPACE_HLG_P3: |
427 |
+ color_encoding.white_point = JXL_WHITE_POINT_D65; |
428 |
+ color_encoding.primaries = JXL_PRIMARIES_P3; |
429 |
+ color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_HLG; |
430 |
+ break; |
431 |
+ default: |
432 |
+ write_color_natively = false; |
433 |
+ break; |
434 |
+ } |
435 |
+ |
436 |
+ if(write_color_natively) |
437 |
+ { |
438 |
+ JxlEncoderSetColorEncoding(encoder, &color_encoding); |
439 |
+ } |
440 |
+ else |
441 |
+ { |
442 |
+ // If we didn't manage to write the color encoding natively we need to fallback to ICC |
443 |
+ dt_print(DT_DEBUG_IMAGEIO, "[jxl] could not generate color encoding structure, falling back to ICC\n"); |
444 |
+ |
445 |
+ cmsUInt32Number icc_size = 0; |
446 |
+ // First find the size of the ICC buffer |
447 |
+ if(!cmsSaveProfileToMem(out_profile, NULL, &icc_size)) JXL_FAIL("error finding ICC data length"); |
448 |
+ if(icc_size > 0) icc_buf = g_malloc(icc_size); |
449 |
+ if(!icc_buf) JXL_FAIL("could not allocate ICC buffer of size %u", icc_size); |
450 |
+ |
451 |
+ // Fill the ICC buffer |
452 |
+ if(!cmsSaveProfileToMem(out_profile, icc_buf, &icc_size)) JXL_FAIL("error writing ICC data"); |
453 |
+ |
454 |
+ LIBJXL_ASSERT(JxlEncoderSetICCProfile(encoder, icc_buf, icc_size)); |
455 |
+ } |
456 |
+ |
457 |
+ // We assume that the user wants the JXL image in a BMFF container. |
458 |
+ // JXL images can be stored without any container so they are smaller, but |
459 |
+ // this removes the possibility of storing extra metadata like Exif and XMP. |
460 |
+ LIBJXL_ASSERT(JxlEncoderUseBoxes(encoder)); |
461 |
+ |
462 |
+ JxlPixelFormat pixel_format = { 3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 }; |
463 |
+ |
464 |
+ // Fix pixel stride |
465 |
+ const size_t pixels_size = width * height * 3 * sizeof(float); |
466 |
+ pixels = g_malloc(pixels_size); |
467 |
+ if(!pixels) JXL_FAIL("could not allocate output pixel buffer of size %zu", pixels_size); |
468 |
+#ifdef _OPENMP |
469 |
+#pragma omp parallel for simd default(none) dt_omp_firstprivate(in_tmp, pixels, width, height) schedule(simd \ |
470 |
+ : static) \ |
471 |
+ collapse(2) |
472 |
+#endif |
473 |
+ for(uint32_t y = 0; y < height; ++y) |
474 |
+ { |
475 |
+ for(uint32_t x = 0; x < width; ++x) |
476 |
+ { |
477 |
+ const float *in_pixel = (const float *)in_tmp + 4 * ((y * width) + x); |
478 |
+ float *out_pixel = pixels + 3 * ((y * width) + x); |
479 |
+ |
480 |
+ out_pixel[0] = in_pixel[0]; |
481 |
+ out_pixel[1] = in_pixel[1]; |
482 |
+ out_pixel[2] = in_pixel[2]; |
483 |
+ } |
484 |
+ } |
485 |
+ |
486 |
+ LIBJXL_ASSERT(JxlEncoderAddImageFrame(frame_settings, &pixel_format, pixels, pixels_size)); |
487 |
+ |
488 |
+ /* TODO: workaround; remove when exiv2 implements JXL BMFF write support and use dt_exif_write_blob() after |
489 |
+ * closing file instead */ |
490 |
+ if(exif && exif_len > 0) |
491 |
+ { |
492 |
+ // Prepend the 4 byte (zero) offset to the blob before writing |
493 |
+ // (as required in the equivalent HEIF/JPEG XS Exif box specs) |
494 |
+ exif_buf = g_malloc0(exif_len + 4); |
495 |
+ if(!exif_buf) JXL_FAIL("could not allocate Exif buffer of size %zu", (size_t)(exif_len + 4)); |
496 |
+ memmove(exif_buf + 4, exif, exif_len); |
497 |
+ // Exiv2 doesn't support Brotli compressed boxes yet |
498 |
+ LIBJXL_ASSERT(JxlEncoderAddBox(encoder, "Exif", exif_buf, exif_len + 4, JXL_FALSE)); |
499 |
+ } |
500 |
+ |
501 |
+ /* TODO: workaround; remove when exiv2 implements JXL BMFF write support and update flags() */ |
502 |
+ xmp_string = dt_exif_xmp_read_string(imgid); |
503 |
+ size_t xmp_len; |
504 |
+ if(xmp_string && (xmp_len = strlen(xmp_string)) > 0) |
505 |
+ { |
506 |
+ // Exiv2 doesn't support Brotli compressed boxes |
507 |
+ LIBJXL_ASSERT(JxlEncoderAddBox(encoder, "xml ", (const uint8_t *)xmp_string, xmp_len, JXL_FALSE)); |
508 |
+ } |
509 |
+ |
510 |
+ // No more image frames nor metadata boxes to add |
511 |
+ JxlEncoderCloseInput(encoder); |
512 |
+ |
513 |
+ // Write the image codestream to a buffer, starting with a chunk of 64 KiB. |
514 |
+ // TODO: Can we better estimate what the optimal size of chunks is for this image? |
515 |
+ size_t chunk_size = 1 << 16; |
516 |
+ size_t out_len = chunk_size; |
517 |
+ out_buf = g_malloc(out_len); |
518 |
+ if(!out_buf) JXL_FAIL("could not allocate codestream buffer of size %zu", out_len); |
519 |
+ uint8_t *out_cur = out_buf; |
520 |
+ size_t out_avail = out_len; |
521 |
+ |
522 |
+ JxlEncoderStatus out_status = JXL_ENC_NEED_MORE_OUTPUT; |
523 |
+ while(out_status == JXL_ENC_NEED_MORE_OUTPUT) |
524 |
+ { |
525 |
+ out_status = JxlEncoderProcessOutput(encoder, &out_cur, &out_avail); |
526 |
+ |
527 |
+ if(out_status == JXL_ENC_NEED_MORE_OUTPUT) |
528 |
+ { |
529 |
+ const size_t offset = out_cur - out_buf; |
530 |
+ if(chunk_size < 1 << 20) chunk_size *= 2; |
531 |
+ out_len += chunk_size; |
532 |
+ out_buf = g_realloc(out_buf, out_len); |
533 |
+ if(!out_buf) |
534 |
+ { |
535 |
+ JXL_FAIL("could not reallocate codestream buffer to size %zu", out_len); |
536 |
+ goto end; |
537 |
+ } |
538 |
+ out_cur = out_buf + offset; |
539 |
+ out_avail = out_len - offset; |
540 |
+ } |
541 |
+ } |
542 |
+ LIBJXL_ASSERT(out_status); |
543 |
+ // Update actual length of codestream written |
544 |
+ out_len = out_cur - out_buf; |
545 |
+ |
546 |
+ // Write codestream contents to file |
547 |
+ out_file = g_fopen(filename, "wb"); |
548 |
+ if(!out_file) JXL_FAIL("could not open output file `%s'", filename); |
549 |
+ |
550 |
+ if(fwrite(out_buf, sizeof(uint8_t), out_len, out_file) != out_len) |
551 |
+ JXL_FAIL("could not write bytes to `%s'", filename); |
552 |
+ |
553 |
+ // Finally, successful write: set to success code |
554 |
+ ret = 0; |
555 |
+ |
556 |
+end: |
557 |
+ if(runner) JxlResizableParallelRunnerDestroy(runner); |
558 |
+ if(encoder) JxlEncoderDestroy(encoder); |
559 |
+ if(out_file) fclose(out_file); |
560 |
+ g_free(pixels); |
561 |
+ g_free(icc_buf); |
562 |
+ g_free(exif_buf); |
563 |
+ g_free(xmp_string); |
564 |
+ g_free(out_buf); |
565 |
+ |
566 |
+ return ret; |
567 |
+} |
568 |
+ |
569 |
+int levels(dt_imageio_module_data_t *data) |
570 |
+{ |
571 |
+ return IMAGEIO_RGB | IMAGEIO_FLOAT; |
572 |
+} |
573 |
+ |
574 |
+int flags(dt_imageio_module_data_t *data) |
575 |
+{ |
576 |
+ /* |
577 |
+ * As of exiv2 0.27.5 there is no write support for the JXL BMFF format, |
578 |
+ * so we do not return the XMP supported flag currently. |
579 |
+ * Once exiv2 write support is there, the flag can be returned, and the |
580 |
+ * direct XMP embedding workaround using JxlEncoderAddBox("xml ") above |
581 |
+ * can be removed. |
582 |
+ */ |
583 |
+ return 0; /* FORMAT_FLAGS_SUPPORT_XMP; */ |
584 |
+} |
585 |
+ |
586 |
+ |
587 |
+size_t params_size(dt_imageio_module_format_t *self) |
588 |
+{ |
589 |
+ return sizeof(dt_imageio_jxl_t); |
590 |
+} |
591 |
+ |
592 |
+void *get_params(dt_imageio_module_format_t *self) |
593 |
+{ |
594 |
+ dt_imageio_jxl_t *d = g_malloc0(sizeof(dt_imageio_jxl_t)); |
595 |
+ |
596 |
+ if(!d) return NULL; |
597 |
+ |
598 |
+ d->bpp = dt_conf_get_int("plugins/imageio/format/jxl/bpp"); |
599 |
+ |
600 |
+ d->quality = dt_conf_get_int("plugins/imageio/format/jxl/quality"); |
601 |
+ |
602 |
+ d->original = dt_conf_get_bool("plugins/imageio/format/jxl/original") & 1; |
603 |
+ |
604 |
+ d->effort = dt_conf_get_int("plugins/imageio/format/jxl/effort"); |
605 |
+ |
606 |
+ d->tier = dt_conf_get_int("plugins/imageio/format/jxl/tier"); |
607 |
+ |
608 |
+ return d; |
609 |
+} |
610 |
+ |
611 |
+void free_params(dt_imageio_module_format_t *self, dt_imageio_module_data_t *params) |
612 |
+{ |
613 |
+ g_free(params); |
614 |
+} |
615 |
+ |
616 |
+int set_params(dt_imageio_module_format_t *self, const void *params, const int size) |
617 |
+{ |
618 |
+ if(size != self->params_size(self)) return 1; |
619 |
+ |
620 |
+ const dt_imageio_jxl_t *d = (dt_imageio_jxl_t *)params; |
621 |
+ dt_imageio_jxl_gui_data_t *g = (dt_imageio_jxl_gui_data_t *)self->gui_data; |
622 |
+ |
623 |
+ int bpp = d->bpp; |
624 |
+ if(bpp < 0) |
625 |
+ bpp = 0; |
626 |
+ else if(bpp > 5) |
627 |
+ bpp = 5; |
628 |
+ dt_bauhaus_combobox_set(g->bpp, bpp); |
629 |
+ |
630 |
+ int quality = d->quality; |
631 |
+ if(quality < 0) |
632 |
+ quality = 0; |
633 |
+ else if(quality > 100) |
634 |
+ quality = 100; |
635 |
+ dt_bauhaus_slider_set(g->quality, quality); |
636 |
+ |
637 |
+ int original = d->original; |
638 |
+ dt_bauhaus_combobox_set(g->original, original & 1); |
639 |
+ |
640 |
+ int effort = d->effort; |
641 |
+ if(effort < 1) |
642 |
+ effort = 1; |
643 |
+ else if(effort > 9) |
644 |
+ effort = 9; |
645 |
+ dt_bauhaus_slider_set(g->effort, effort); |
646 |
+ |
647 |
+ int tier = d->tier; |
648 |
+ if(tier < 0) |
649 |
+ tier = 0; |
650 |
+ else if(tier > 4) |
651 |
+ tier = 4; |
652 |
+ dt_bauhaus_slider_set(g->tier, tier); |
653 |
+ |
654 |
+ return 0; |
655 |
+} |
656 |
+ |
657 |
+ |
658 |
+const char *name() |
659 |
+{ |
660 |
+ return _("JPEG XL"); |
661 |
+} |
662 |
+ |
663 |
+static void bpp_changed(GtkWidget *bpp, dt_imageio_module_format_t *self) |
664 |
+{ |
665 |
+ const int bpp_enum = dt_bauhaus_combobox_get(bpp); |
666 |
+ dt_conf_set_int("plugins/imageio/format/jxl/bpp", bpp_enum); |
667 |
+ |
668 |
+ dt_imageio_jxl_gui_data_t *g = (dt_imageio_jxl_gui_data_t *)self->gui_data; |
669 |
+ const int quality_val = (int)dt_bauhaus_slider_get(g->quality); |
670 |
+ |
671 |
+ if(bpp_enum < 4 && quality_val == 100) |
672 |
+ { |
673 |
+ dt_bauhaus_combobox_set(g->original, 1); |
674 |
+ gtk_widget_set_sensitive(g->original, FALSE); |
675 |
+ } |
676 |
+ else |
677 |
+ gtk_widget_set_sensitive(g->original, TRUE); |
678 |
+} |
679 |
+ |
680 |
+static void quality_changed(GtkWidget *quality, dt_imageio_module_format_t *self) |
681 |
+{ |
682 |
+ const int quality_val = (int)dt_bauhaus_slider_get(quality); |
683 |
+ dt_conf_set_int("plugins/imageio/format/jxl/quality", quality_val); |
684 |
+ |
685 |
+ dt_imageio_jxl_gui_data_t *g = (dt_imageio_jxl_gui_data_t *)self->gui_data; |
686 |
+ const int bpp_enum = dt_bauhaus_combobox_get(g->bpp); |
687 |
+ |
688 |
+ if(bpp_enum < 4 && quality_val == 100) |
689 |
+ { |
690 |
+ dt_bauhaus_combobox_set(g->original, 1); |
691 |
+ gtk_widget_set_sensitive(g->original, FALSE); |
692 |
+ } |
693 |
+ else |
694 |
+ gtk_widget_set_sensitive(g->original, TRUE); |
695 |
+} |
696 |
+ |
697 |
+static void original_changed(GtkWidget *original, dt_imageio_module_format_t *self) |
698 |
+{ |
699 |
+ dt_conf_set_bool("plugins/imageio/format/jxl/original", dt_bauhaus_combobox_get(original)); |
700 |
+} |
701 |
+ |
702 |
+static void effort_changed(GtkWidget *effort, dt_imageio_module_format_t *self) |
703 |
+{ |
704 |
+ dt_conf_set_int("plugins/imageio/format/jxl/effort", (int)dt_bauhaus_slider_get(effort)); |
705 |
+} |
706 |
+ |
707 |
+static void tier_changed(GtkWidget *tier, dt_imageio_module_format_t *self) |
708 |
+{ |
709 |
+ dt_conf_set_int("plugins/imageio/format/jxl/tier", (int)dt_bauhaus_slider_get(tier)); |
710 |
+} |
711 |
+ |
712 |
+void gui_init(dt_imageio_module_format_t *self) |
713 |
+{ |
714 |
+ dt_imageio_jxl_gui_data_t *gui = g_malloc0(sizeof(dt_imageio_jxl_gui_data_t)); |
715 |
+ if(!gui) return; |
716 |
+ self->gui_data = gui; |
717 |
+ |
718 |
+ GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); |
719 |
+ self->widget = box; |
720 |
+ |
721 |
+ // bits per sample combobox |
722 |
+ GtkWidget *bpp = dt_bauhaus_combobox_new(NULL); |
723 |
+ dt_bauhaus_combobox_add(bpp, _("8 bit")); |
724 |
+ dt_bauhaus_combobox_add(bpp, _("10 bit")); |
725 |
+ dt_bauhaus_combobox_add(bpp, _("12 bit")); |
726 |
+ dt_bauhaus_combobox_add(bpp, _("16 bit")); |
727 |
+ dt_bauhaus_combobox_add(bpp, _("16 bit (half)")); |
728 |
+ dt_bauhaus_combobox_add(bpp, _("32 bit (float)")); |
729 |
+ const int bpp_enum = dt_conf_get_int("plugins/imageio/format/jxl/bpp"); |
730 |
+ dt_bauhaus_combobox_set(bpp, bpp_enum); |
731 |
+ dt_bauhaus_widget_set_label(bpp, NULL, N_("bit depth")); |
732 |
+ g_signal_connect(G_OBJECT(bpp), "value-changed", G_CALLBACK(bpp_changed), self); |
733 |
+ gtk_box_pack_start(GTK_BOX(box), bpp, TRUE, TRUE, 0); |
734 |
+ gui->bpp = bpp; |
735 |
+ |
736 |
+ // quality slider |
737 |
+ GtkWidget *quality |
738 |
+ = dt_bauhaus_slider_new_with_range(NULL, dt_confgen_get_int("plugins/imageio/format/jxl/quality", DT_MIN), |
739 |
+ dt_confgen_get_int("plugins/imageio/format/jxl/quality", DT_MAX), 1, |
740 |
+ dt_confgen_get_int("plugins/imageio/format/jxl/quality", DT_DEFAULT), 0); |
741 |
+ const int quality_val = dt_conf_get_int("plugins/imageio/format/jxl/quality"); |
742 |
+ dt_bauhaus_slider_set(quality, quality_val); |
743 |
+ dt_bauhaus_widget_set_label(quality, NULL, _("quality")); |
744 |
+ gtk_widget_set_tooltip_text(quality, _("the quality of the output image\n0-29 = very lossy\n30-99 = JPEG " |
745 |
+ "quality comparable\n100 = lossless (integer bith depth only)")); |
746 |
+ g_signal_connect(G_OBJECT(quality), "value-changed", G_CALLBACK(quality_changed), self); |
747 |
+ gtk_box_pack_start(GTK_BOX(box), quality, TRUE, TRUE, 0); |
748 |
+ gui->quality = quality; |
749 |
+ |
750 |
+ // encoding color profile combobox |
751 |
+ GtkWidget *original = dt_bauhaus_combobox_new(NULL); |
752 |
+ dt_bauhaus_combobox_add(original, _("internal")); |
753 |
+ dt_bauhaus_combobox_add(original, _("original")); |
754 |
+ dt_bauhaus_combobox_set_default(original, |
755 |
+ dt_confgen_get_bool("plugins/imageio/format/jxl/original", DT_DEFAULT) & 1); |
756 |
+ if(bpp_enum < 4 && quality_val == 100) |
757 |
+ { |
758 |
+ dt_bauhaus_combobox_set(original, 1); |
759 |
+ gtk_widget_set_sensitive(original, FALSE); |
760 |
+ } |
761 |
+ else |
762 |
+ dt_bauhaus_combobox_set(original, dt_conf_get_bool("plugins/imageio/format/jxl/original") & 1); |
763 |
+ dt_bauhaus_widget_set_label(original, NULL, N_("encoding color profile")); |
764 |
+ g_signal_connect(G_OBJECT(original), "value-changed", G_CALLBACK(original_changed), self); |
765 |
+ gtk_box_pack_start(GTK_BOX(box), original, TRUE, TRUE, 0); |
766 |
+ gui->original = original; |
767 |
+ |
768 |
+ // encoding effort slider |
769 |
+ GtkWidget *effort |
770 |
+ = dt_bauhaus_slider_new_with_range(NULL, dt_confgen_get_int("plugins/imageio/format/jxl/effort", DT_MIN), |
771 |
+ dt_confgen_get_int("plugins/imageio/format/jxl/effort", DT_MAX), 1, |
772 |
+ dt_confgen_get_int("plugins/imageio/format/jxl/effort", DT_DEFAULT), 0); |
773 |
+ dt_bauhaus_slider_set(effort, dt_conf_get_int("plugins/imageio/format/jxl/effort")); |
774 |
+ dt_bauhaus_widget_set_label(effort, NULL, _("encoding effort")); |
775 |
+ gtk_widget_set_tooltip_text(effort, _("the effort used to encode the image, higher efforts will have " |
776 |
+ "better results at the expense of longer encode times")); |
777 |
+ g_signal_connect(G_OBJECT(effort), "value-changed", G_CALLBACK(effort_changed), self); |
778 |
+ gtk_box_pack_start(GTK_BOX(box), effort, TRUE, TRUE, 0); |
779 |
+ gui->effort = effort; |
780 |
+ |
781 |
+ // decoding speed (tier) slider |
782 |
+ GtkWidget *tier |
783 |
+ = dt_bauhaus_slider_new_with_range(NULL, dt_confgen_get_int("plugins/imageio/format/jxl/tier", DT_MIN), |
784 |
+ dt_confgen_get_int("plugins/imageio/format/jxl/tier", DT_MAX), 1, |
785 |
+ dt_confgen_get_int("plugins/imageio/format/jxl/tier", DT_DEFAULT), 0); |
786 |
+ dt_bauhaus_slider_set(tier, dt_conf_get_int("plugins/imageio/format/jxl/tier")); |
787 |
+ dt_bauhaus_widget_set_label(tier, NULL, _("decoding speed")); |
788 |
+ gtk_widget_set_tooltip_text(tier, _("the preffered decoding speed with some sacrifice of quality")); |
789 |
+ g_signal_connect(G_OBJECT(tier), "value-changed", G_CALLBACK(tier_changed), self); |
790 |
+ gtk_box_pack_start(GTK_BOX(box), tier, TRUE, TRUE, 0); |
791 |
+ gui->tier = tier; |
792 |
+} |
793 |
+ |
794 |
+void gui_cleanup(dt_imageio_module_format_t *self) |
795 |
+{ |
796 |
+ g_free(self->gui_data); |
797 |
+} |
798 |
+ |
799 |
+void gui_reset(dt_imageio_module_format_t *self) |
800 |
+{ |
801 |
+ dt_imageio_jxl_gui_data_t *gui = (dt_imageio_jxl_gui_data_t *)self->gui_data; |
802 |
+ |
803 |
+ const int bpp = dt_confgen_get_int("plugins/imageio/format/jxl/bpp", DT_DEFAULT); |
804 |
+ dt_bauhaus_combobox_set(gui->bpp, bpp); |
805 |
+ |
806 |
+ const int quality = dt_confgen_get_int("plugins/imageio/format/jxl/quality", DT_DEFAULT); |
807 |
+ dt_bauhaus_slider_set(gui->quality, quality); |
808 |
+ |
809 |
+ const int original = dt_confgen_get_bool("plugins/imageio/format/jxl/original", DT_DEFAULT); |
810 |
+ dt_bauhaus_combobox_set(gui->original, original & 1); |
811 |
+ |
812 |
+ const int effort = dt_confgen_get_int("plugins/imageio/format/jxl/effort", DT_DEFAULT); |
813 |
+ dt_bauhaus_slider_set(gui->effort, effort); |
814 |
+ |
815 |
+ const int tier = dt_confgen_get_int("plugins/imageio/format/jxl/tier", DT_DEFAULT); |
816 |
+ dt_bauhaus_slider_set(gui->tier, tier); |
817 |
+} |
818 |
+ |
819 |
+// clang-format off |
820 |
+// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py |
821 |
+// vim: shiftwidth=2 expandtab tabstop=2 cindent |
822 |
+// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; |
823 |
+// clang-format on |
824 |
--- src/imageio/format/tiff.c.orig 2022-02-09 10:21:58 UTC |
825 |
+++ src/imageio/format/tiff.c |
826 |
@@ -823,6 +823,8 @@ void gui_init(dt_imageio_module_format_t *self) |
827 |
dt_bauhaus_combobox_add(gui->compress, _("uncompressed")); |
828 |
dt_bauhaus_combobox_add(gui->compress, _("deflate")); |
829 |
dt_bauhaus_combobox_add(gui->compress, _("deflate with predictor")); |
830 |
+ dt_bauhaus_combobox_set_default(gui->compress, |
831 |
+ dt_confgen_get_int("plugins/imageio/format/tiff/compress", DT_DEFAULT)); |
832 |
dt_bauhaus_combobox_set(gui->compress, compress); |
833 |
gtk_box_pack_start(GTK_BOX(self->widget), gui->compress, TRUE, TRUE, 0); |
834 |
|
835 |
@@ -862,6 +864,7 @@ void gui_reset(dt_imageio_module_format_t *self) |
836 |
{ |
837 |
dt_imageio_tiff_gui_t *gui = (dt_imageio_tiff_gui_t *)self->gui_data; |
838 |
dt_bauhaus_combobox_set(gui->bpp, 0); //8bpp |
839 |
+ dt_bauhaus_combobox_set(gui->compress, dt_confgen_get_int("plugins/imageio/format/tiff/compress", DT_DEFAULT)); |
840 |
dt_bauhaus_slider_set(gui->compresslevel, dt_confgen_get_int("plugins/imageio/format/tiff/compresslevel", DT_DEFAULT)); |
841 |
dt_bauhaus_combobox_set(gui->shortfiles, dt_confgen_get_int("plugins/imageio/format/tiff/shortfile", DT_DEFAULT)); |
842 |
} |