1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
5
6 #include "plugins/gimp/file-jxl-load.h"
7
8 // Defined by both FUIF and glib.
9 #undef MAX
10 #undef MIN
11 #undef CLAMP
12
13 #include "lib/jxl/alpha.h"
14 #include "lib/jxl/base/file_io.h"
15 #include "lib/jxl/base/thread_pool_internal.h"
16 #include "lib/jxl/dec_file.h"
17 #include "plugins/gimp/common.h"
18
19 namespace jxl {
20
21 namespace {
22
23 template <GimpPrecision precision, bool has_alpha, size_t num_channels>
FillBuffer(const CodecInOut & io,std::vector<typename BufferFormat<precision>::Sample> * const pixel_data)24 void FillBuffer(
25 const CodecInOut& io,
26 std::vector<typename BufferFormat<precision>::Sample>* const pixel_data) {
27 pixel_data->reserve(io.xsize() * io.ysize() * (num_channels + has_alpha));
28 for (size_t y = 0; y < io.ysize(); ++y) {
29 const float* rows[num_channels];
30 for (size_t c = 0; c < num_channels; ++c) {
31 rows[c] = io.Main().color().ConstPlaneRow(c, y);
32 }
33 const float* const alpha_row =
34 has_alpha ? io.Main().alpha().ConstRow(y) : nullptr;
35 for (size_t x = 0; x < io.xsize(); ++x) {
36 const float alpha = has_alpha ? alpha_row[x] : 1.f;
37 const float alpha_multiplier =
38 has_alpha && io.Main().AlphaIsPremultiplied()
39 ? 1.f / std::max(kSmallAlpha, alpha)
40 : 1.f;
41 for (const float* const row : rows) {
42 pixel_data->push_back(BufferFormat<precision>::FromFloat(
43 std::max(0.f, std::min(1.f, alpha_multiplier * row[x]))));
44 }
45 if (has_alpha) {
46 pixel_data->push_back(
47 BufferFormat<precision>::FromFloat(255.f * alpha));
48 }
49 }
50 }
51 }
52
53 template <GimpPrecision precision>
FillGimpLayer(const gint32 layer,const CodecInOut & io,GimpImageType layer_type)54 Status FillGimpLayer(const gint32 layer, const CodecInOut& io,
55 GimpImageType layer_type) {
56 std::vector<typename BufferFormat<precision>::Sample> pixel_data;
57 switch (layer_type) {
58 case GIMP_GRAY_IMAGE:
59 FillBuffer<precision, /*has_alpha=*/false, /*num_channels=*/1>(
60 io, &pixel_data);
61 break;
62 case GIMP_GRAYA_IMAGE:
63 FillBuffer<precision, /*has_alpha=*/true, /*num_channels=*/1>(
64 io, &pixel_data);
65 break;
66 case GIMP_RGB_IMAGE:
67 FillBuffer<precision, /*has_alpha=*/false, /*num_channels=*/3>(
68 io, &pixel_data);
69 break;
70 case GIMP_RGBA_IMAGE:
71 FillBuffer<precision, /*has_alpha=*/true, /*num_channels=*/3>(
72 io, &pixel_data);
73 break;
74 default:
75 return false;
76 }
77
78 GeglBuffer* buffer = gimp_drawable_get_buffer(layer);
79 gegl_buffer_set(buffer, GEGL_RECTANGLE(0, 0, io.xsize(), io.ysize()), 0,
80 nullptr, pixel_data.data(), GEGL_AUTO_ROWSTRIDE);
81 g_clear_object(&buffer);
82 return true;
83 }
84
85 } // namespace
86
LoadJpegXlImage(const gchar * const filename,gint32 * const image_id)87 Status LoadJpegXlImage(const gchar* const filename, gint32* const image_id) {
88 PaddedBytes compressed;
89 JXL_RETURN_IF_ERROR(ReadFile(filename, &compressed));
90
91 // TODO(deymo): Use C API instead of the ThreadPoolInternal.
92 ThreadPoolInternal pool;
93 DecompressParams dparams;
94 CodecInOut io;
95 JXL_RETURN_IF_ERROR(DecodeFile(dparams, compressed, &io, &pool));
96
97 const ColorEncoding& color_encoding = io.metadata.m.color_encoding;
98 JXL_RETURN_IF_ERROR(io.TransformTo(color_encoding, &pool));
99
100 GimpColorProfile* profile = nullptr;
101 if (color_encoding.IsSRGB()) {
102 profile = gimp_color_profile_new_rgb_srgb();
103 } else if (color_encoding.IsLinearSRGB()) {
104 profile = gimp_color_profile_new_rgb_srgb_linear();
105 } else {
106 profile = gimp_color_profile_new_from_icc_profile(
107 color_encoding.ICC().data(), color_encoding.ICC().size(),
108 /*error=*/nullptr);
109 }
110 if (profile == nullptr) {
111 return JXL_FAILURE(
112 "Failed to create GIMP color profile from %zu bytes of ICC data",
113 color_encoding.ICC().size());
114 }
115
116 GimpImageBaseType image_type;
117 GimpImageType layer_type;
118
119 if (io.Main().IsGray()) {
120 image_type = GIMP_GRAY;
121 if (io.Main().HasAlpha()) {
122 layer_type = GIMP_GRAYA_IMAGE;
123 } else {
124 layer_type = GIMP_GRAY_IMAGE;
125 }
126 } else {
127 image_type = GIMP_RGB;
128 if (io.Main().HasAlpha()) {
129 layer_type = GIMP_RGBA_IMAGE;
130 } else {
131 layer_type = GIMP_RGB_IMAGE;
132 }
133 }
134
135 GimpPrecision precision;
136 Status (*fill_layer)(gint32 layer, const CodecInOut& io, GimpImageType);
137 if (io.metadata.m.bit_depth.floating_point_sample) {
138 if (io.metadata.m.bit_depth.bits_per_sample <= 16) {
139 precision = GIMP_PRECISION_HALF_GAMMA;
140 fill_layer = &FillGimpLayer<GIMP_PRECISION_HALF_GAMMA>;
141 } else {
142 precision = GIMP_PRECISION_FLOAT_GAMMA;
143 fill_layer = &FillGimpLayer<GIMP_PRECISION_FLOAT_GAMMA>;
144 }
145 } else {
146 if (io.metadata.m.bit_depth.bits_per_sample <= 8) {
147 precision = GIMP_PRECISION_U8_GAMMA;
148 fill_layer = &FillGimpLayer<GIMP_PRECISION_U8_GAMMA>;
149 } else if (io.metadata.m.bit_depth.bits_per_sample <= 16) {
150 precision = GIMP_PRECISION_U16_GAMMA;
151 fill_layer = &FillGimpLayer<GIMP_PRECISION_U16_GAMMA>;
152 } else {
153 precision = GIMP_PRECISION_U32_GAMMA;
154 fill_layer = &FillGimpLayer<GIMP_PRECISION_U32_GAMMA>;
155 }
156 }
157
158 *image_id = gimp_image_new_with_precision(io.xsize(), io.ysize(), image_type,
159 precision);
160 gimp_image_set_color_profile(*image_id, profile);
161 g_clear_object(&profile);
162 const gint32 layer = gimp_layer_new(
163 *image_id, "image", io.xsize(), io.ysize(), layer_type, /*opacity=*/100,
164 gimp_image_get_default_new_layer_mode(*image_id));
165 gimp_image_set_filename(*image_id, filename);
166 gimp_image_insert_layer(*image_id, layer, /*parent_id=*/-1, /*position=*/0);
167
168 JXL_RETURN_IF_ERROR(fill_layer(layer, io, layer_type));
169
170 return true;
171 }
172
173 } // namespace jxl
174