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