Coverage Report

Created: 2026-06-30 07:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/extras/enc/jpg.cc
Line
Count
Source
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 "lib/extras/enc/jpg.h"
7
8
#include <memory>
9
10
#include "lib/extras/enc/encode.h"
11
12
#if !JPEGXL_ENABLE_JPEG
13
14
namespace jxl {
15
namespace extras {
16
0
std::unique_ptr<Encoder> GetJPEGEncoder() { return nullptr; }
17
}  // namespace extras
18
}  // namespace jxl
19
20
#else  // JPEGXL_ENABLE_JPEG
21
22
#include <jxl/codestream_header.h>
23
#include <jxl/color_encoding.h>
24
#include <jxl/types.h>
25
26
#include <algorithm>
27
#include <array>
28
#include <cmath>
29
#include <cstddef>
30
#include <cstdint>
31
#include <cstdlib>
32
#include <cstring>
33
#include <fstream>
34
#include <sstream>
35
#include <string>
36
#include <utility>
37
#include <vector>
38
39
#include "lib/extras/exif.h"
40
#include "lib/extras/include_jpeglib.h"
41
#include "lib/extras/packed_image.h"
42
#include "lib/jxl/base/common.h"
43
#include "lib/jxl/base/data_parallel.h"
44
#include "lib/jxl/base/sanitizers.h"
45
#include "lib/jxl/base/status.h"
46
47
#if JPEGXL_ENABLE_SJPEG
48
#include "sjpeg.h"
49
#include "sjpegi.h"
50
#endif
51
52
namespace jxl {
53
namespace extras {
54
namespace {
55
56
constexpr unsigned char kICCSignature[12] = {
57
    0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
58
constexpr int kICCMarker = JPEG_APP0 + 2;
59
constexpr size_t kMaxBytesInMarker = 65533;
60
61
constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
62
                                             0x66, 0x00, 0x00};
63
constexpr int kExifMarker = JPEG_APP0 + 1;
64
65
enum class JpegEncoder {
66
  kLibJpeg,
67
  kSJpeg,
68
};
69
70
// Popular jpeg scan scripts
71
// The fields of the individual scans are:
72
// comps_in_scan, component_index[], Ss, Se, Ah, Al
73
constexpr auto kScanScript1 = to_array<jpeg_scan_info>({
74
    {1, {0}, 0, 0, 0, 0},   //
75
    {1, {1}, 0, 0, 0, 0},   //
76
    {1, {2}, 0, 0, 0, 0},   //
77
    {1, {0}, 1, 8, 0, 0},   //
78
    {1, {0}, 9, 63, 0, 0},  //
79
    {1, {1}, 1, 63, 0, 0},  //
80
    {1, {2}, 1, 63, 0, 0}   //
81
});
82
constexpr size_t kNumScans1 = kScanScript1.size();
83
84
constexpr auto kScanScript2 = to_array<jpeg_scan_info>({
85
    {1, {0}, 0, 0, 0, 0},   //
86
    {1, {1}, 0, 0, 0, 0},   //
87
    {1, {2}, 0, 0, 0, 0},   //
88
    {1, {0}, 1, 2, 0, 1},   //
89
    {1, {0}, 3, 63, 0, 1},  //
90
    {1, {0}, 1, 63, 1, 0},  //
91
    {1, {1}, 1, 63, 0, 0},  //
92
    {1, {2}, 1, 63, 0, 0}   //
93
});
94
constexpr size_t kNumScans2 = kScanScript2.size();
95
96
constexpr auto kScanScript3 = to_array<jpeg_scan_info>({
97
    {1, {0}, 0, 0, 0, 0},   //
98
    {1, {1}, 0, 0, 0, 0},   //
99
    {1, {2}, 0, 0, 0, 0},   //
100
    {1, {0}, 1, 63, 0, 2},  //
101
    {1, {0}, 1, 63, 2, 1},  //
102
    {1, {0}, 1, 63, 1, 0},  //
103
    {1, {1}, 1, 63, 0, 0},  //
104
    {1, {2}, 1, 63, 0, 0}   //
105
});
106
constexpr size_t kNumScans3 = kScanScript3.size();
107
108
constexpr auto kScanScript4 = to_array<jpeg_scan_info>({
109
    {3, {0, 1, 2}, 0, 0, 0, 1},  //
110
    {1, {0}, 1, 5, 0, 2},        //
111
    {1, {2}, 1, 63, 0, 1},       //
112
    {1, {1}, 1, 63, 0, 1},       //
113
    {1, {0}, 6, 63, 0, 2},       //
114
    {1, {0}, 1, 63, 2, 1},       //
115
    {3, {0, 1, 2}, 0, 0, 1, 0},  //
116
    {1, {2}, 1, 63, 1, 0},       //
117
    {1, {1}, 1, 63, 1, 0},       //
118
    {1, {0}, 1, 63, 1, 0}        //
119
});
120
constexpr size_t kNumScans4 = kScanScript4.size();
121
122
constexpr auto kScanScript5 = to_array<jpeg_scan_info>({
123
    {3, {0, 1, 2}, 0, 0, 0, 1},  //
124
    {1, {0}, 1, 5, 0, 2},        //
125
    {1, {1}, 1, 5, 0, 2},        //
126
    {1, {2}, 1, 5, 0, 2},        //
127
    {1, {1}, 6, 63, 0, 2},       //
128
    {1, {2}, 6, 63, 0, 2},       //
129
    {1, {0}, 6, 63, 0, 2},       //
130
    {1, {0}, 1, 63, 2, 1},       //
131
    {1, {1}, 1, 63, 2, 1},       //
132
    {1, {2}, 1, 63, 2, 1},       //
133
    {3, {0, 1, 2}, 0, 0, 1, 0},  //
134
    {1, {0}, 1, 63, 1, 0},       //
135
    {1, {1}, 1, 63, 1, 0},       //
136
    {1, {2}, 1, 63, 1, 0}        //
137
});
138
constexpr size_t kNumScans5 = kScanScript5.size();
139
140
// default progressive mode of jpegli
141
constexpr auto kScanScript6 = to_array<jpeg_scan_info>({
142
    {3, {0, 1, 2}, 0, 0, 0, 0},  //
143
    {1, {0}, 1, 2, 0, 0},        //
144
    {1, {1}, 1, 2, 0, 0},        //
145
    {1, {2}, 1, 2, 0, 0},        //
146
    {1, {0}, 3, 63, 0, 2},       //
147
    {1, {1}, 3, 63, 0, 2},       //
148
    {1, {2}, 3, 63, 0, 2},       //
149
    {1, {0}, 3, 63, 2, 1},       //
150
    {1, {1}, 3, 63, 2, 1},       //
151
    {1, {2}, 3, 63, 2, 1},       //
152
    {1, {0}, 3, 63, 1, 0},       //
153
    {1, {1}, 3, 63, 1, 0},       //
154
    {1, {2}, 3, 63, 1, 0},       //
155
});
156
constexpr size_t kNumScans6 = kScanScript6.size();
157
158
// Adapt RGB scan info to grayscale jpegs.
159
void FilterScanComponents(const jpeg_compress_struct* cinfo,
160
                          jpeg_scan_info* si) {
161
  const int all_comps_in_scan = si->comps_in_scan;
162
  si->comps_in_scan = 0;
163
  for (int j = 0; j < all_comps_in_scan; ++j) {
164
    const int component = si->component_index[j];
165
    if (component < cinfo->input_components) {
166
      si->component_index[si->comps_in_scan++] = component;
167
    }
168
  }
169
}
170
171
Status SetJpegProgression(int progressive_id,
172
                          std::vector<jpeg_scan_info>* scan_infos,
173
                          jpeg_compress_struct* cinfo) {
174
  if (progressive_id < 0) {
175
    return true;
176
  }
177
  if (progressive_id == 0) {
178
    jpeg_simple_progression(cinfo);
179
    return true;
180
  }
181
  const jpeg_scan_info* kScanScripts[] = {
182
      kScanScript1.data(), kScanScript2.data(), kScanScript3.data(),
183
      kScanScript4.data(), kScanScript5.data(), kScanScript6.data()};
184
  constexpr auto kNumScans = to_array<size_t>(
185
      {kNumScans1, kNumScans2, kNumScans3, kNumScans4, kNumScans5, kNumScans6});
186
  if (progressive_id > static_cast<int>(kNumScans.size())) {
187
    return JXL_FAILURE("Unknown jpeg scan script id %d", progressive_id);
188
  }
189
  const jpeg_scan_info* scan_script = kScanScripts[progressive_id - 1];
190
  const size_t num_scans = kNumScans[progressive_id - 1];
191
  // filter scan script for number of components
192
  for (size_t i = 0; i < num_scans; ++i) {
193
    jpeg_scan_info scan_info = scan_script[i];
194
    FilterScanComponents(cinfo, &scan_info);
195
    if (scan_info.comps_in_scan > 0) {
196
      scan_infos->emplace_back(scan_info);
197
    }
198
  }
199
  cinfo->scan_info = scan_infos->data();
200
  cinfo->num_scans = scan_infos->size();
201
  return true;
202
}
203
204
void WriteICCProfile(jpeg_compress_struct* const cinfo,
205
                     const std::vector<uint8_t>& icc) {
206
  constexpr size_t kMaxIccBytesInMarker =
207
      kMaxBytesInMarker - sizeof kICCSignature - 2;
208
  const int num_markers =
209
      static_cast<int>(DivCeil(icc.size(), kMaxIccBytesInMarker));
210
  size_t begin = 0;
211
  for (int current_marker = 0; current_marker < num_markers; ++current_marker) {
212
    const size_t length = std::min(kMaxIccBytesInMarker, icc.size() - begin);
213
    jpeg_write_m_header(
214
        cinfo, kICCMarker,
215
        static_cast<unsigned int>(length + sizeof kICCSignature + 2));
216
    for (const unsigned char c : kICCSignature) {
217
      jpeg_write_m_byte(cinfo, c);
218
    }
219
    jpeg_write_m_byte(cinfo, current_marker + 1);
220
    jpeg_write_m_byte(cinfo, num_markers);
221
    for (size_t i = 0; i < length; ++i) {
222
      jpeg_write_m_byte(cinfo, icc[begin]);
223
      ++begin;
224
    }
225
  }
226
}
227
void WriteExif(jpeg_compress_struct* const cinfo,
228
               const std::vector<uint8_t>& exif) {
229
  jpeg_write_m_header(
230
      cinfo, kExifMarker,
231
      static_cast<unsigned int>(exif.size() + sizeof kExifSignature));
232
  for (const unsigned char c : kExifSignature) {
233
    jpeg_write_m_byte(cinfo, c);
234
  }
235
  for (uint8_t c : exif) {
236
    jpeg_write_m_byte(cinfo, c);
237
  }
238
}
239
240
Status SetChromaSubsampling(const std::string& subsampling,
241
                            jpeg_compress_struct* const cinfo) {
242
  const std::pair<const char*,
243
                  std::pair<std::array<uint8_t, 3>, std::array<uint8_t, 3> > >
244
      options[] = {{"444", {{{1, 1, 1}}, {{1, 1, 1}}}},
245
                   {"420", {{{2, 1, 1}}, {{2, 1, 1}}}},
246
                   {"422", {{{2, 1, 1}}, {{1, 1, 1}}}},
247
                   {"440", {{{1, 1, 1}}, {{2, 1, 1}}}}};
248
  for (const auto& option : options) {
249
    if (subsampling == option.first) {
250
      for (size_t i = 0; i < 3; i++) {
251
        cinfo->comp_info[i].h_samp_factor = option.second.first[i];
252
        cinfo->comp_info[i].v_samp_factor = option.second.second[i];
253
      }
254
      return true;
255
    }
256
  }
257
  return false;
258
}
259
260
struct JpegParams {
261
  // Common between sjpeg and libjpeg
262
  int quality = 100;
263
  std::string chroma_subsampling = "444";
264
  // Libjpeg parameters
265
  int progressive_id = -1;
266
  bool optimize_coding = true;
267
  bool is_xyb = false;
268
  // Sjpeg parameters
269
  int libjpeg_quality = 0;
270
  std::string libjpeg_chroma_subsampling = "444";
271
  float psnr_target = 0;
272
  std::string custom_base_quant_fn;
273
  float search_q_start = 65.0f;
274
  float search_q_min = 1.0f;
275
  float search_q_max = 100.0f;
276
  int search_max_iters = 20;
277
  float search_tolerance = 0.1f;
278
  float search_q_precision = 0.01f;
279
  float search_first_iter_slope = 3.0f;
280
  bool enable_adaptive_quant = true;
281
};
282
283
Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
284
                         const std::vector<uint8_t>& icc,
285
                         std::vector<uint8_t> exif, const JpegParams& params,
286
                         std::vector<uint8_t>* bytes) {
287
  if (BITS_IN_JSAMPLE != 8 || sizeof(JSAMPLE) != 1) {
288
    return JXL_FAILURE("Only 8 bit JSAMPLE is supported.");
289
  }
290
  jpeg_compress_struct cinfo = {};
291
  jpeg_error_mgr jerr;
292
  cinfo.err = jpeg_std_error(&jerr);
293
  jpeg_create_compress(&cinfo);
294
  unsigned char* buffer = nullptr;
295
#ifdef LIBJPEG_TURBO_VERSION
296
  unsigned long size = 0;  // NOLINT
297
#else
298
  size_t size = 0;  // NOLINT
299
#endif
300
  jpeg_mem_dest(&cinfo, &buffer, &size);
301
  cinfo.image_width = image.xsize;
302
  cinfo.image_height = image.ysize;
303
  cinfo.input_components = info.num_color_channels;
304
  cinfo.in_color_space = info.num_color_channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
305
  jpeg_set_defaults(&cinfo);
306
  cinfo.optimize_coding = static_cast<boolean>(params.optimize_coding);
307
  if (cinfo.input_components == 3) {
308
    JXL_RETURN_IF_ERROR(
309
        SetChromaSubsampling(params.chroma_subsampling, &cinfo));
310
  }
311
  if (params.is_xyb) {
312
    // Tell libjpeg not to convert XYB data to YCbCr.
313
    jpeg_set_colorspace(&cinfo, JCS_RGB);
314
  }
315
  jpeg_set_quality(&cinfo, params.quality, TRUE);
316
  std::vector<jpeg_scan_info> scan_infos;
317
  JXL_RETURN_IF_ERROR(
318
      SetJpegProgression(params.progressive_id, &scan_infos, &cinfo));
319
  jpeg_start_compress(&cinfo, TRUE);
320
  if (!icc.empty()) {
321
    WriteICCProfile(&cinfo, icc);
322
  }
323
  if (!exif.empty()) {
324
    ResetExifOrientation(exif);
325
    WriteExif(&cinfo, exif);
326
  }
327
  if (cinfo.input_components > 3 || cinfo.input_components < 0)
328
    return JXL_FAILURE("invalid numbers of components");
329
330
  std::vector<uint8_t> row_bytes(image.stride);
331
  const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
332
  if (cinfo.num_components == static_cast<int>(image.format.num_channels) &&
333
      image.format.data_type == JXL_TYPE_UINT8) {
334
    for (size_t y = 0; y < image.ysize; ++y) {
335
      memcpy(row_bytes.data(), pixels + y * image.stride, image.stride);
336
      JSAMPROW row[] = {row_bytes.data()};
337
      jpeg_write_scanlines(&cinfo, row, 1);
338
    }
339
  } else if (image.format.data_type == JXL_TYPE_UINT8) {
340
    for (size_t y = 0; y < image.ysize; ++y) {
341
      const uint8_t* image_row = pixels + y * image.stride;
342
      for (size_t x = 0; x < image.xsize; ++x) {
343
        const uint8_t* image_pixel = image_row + x * image.pixel_stride();
344
        memcpy(&row_bytes[x * cinfo.num_components], image_pixel,
345
               cinfo.num_components);
346
      }
347
      JSAMPROW row[] = {row_bytes.data()};
348
      jpeg_write_scanlines(&cinfo, row, 1);
349
    }
350
  } else {
351
    for (size_t y = 0; y < image.ysize; ++y) {
352
      const uint8_t* image_row = pixels + y * image.stride;
353
      for (size_t x = 0; x < image.xsize; ++x) {
354
        const uint8_t* image_pixel = image_row + x * image.pixel_stride();
355
        for (int c = 0; c < cinfo.num_components; ++c) {
356
          uint32_t val16 = (image_pixel[2 * c] << 8) + image_pixel[2 * c + 1];
357
          row_bytes[x * cinfo.num_components + c] = (val16 + 128) / 257;
358
        }
359
      }
360
      JSAMPROW row[] = {row_bytes.data()};
361
      jpeg_write_scanlines(&cinfo, row, 1);
362
    }
363
  }
364
  jpeg_finish_compress(&cinfo);
365
  jpeg_destroy_compress(&cinfo);
366
  bytes->resize(size);
367
  // Compressed image data is initialized by libjpeg, which we are not
368
  // instrumenting with msan.
369
  msan::UnpoisonMemory(buffer, size);
370
  std::copy_n(buffer, size, bytes->data());
371
  std::free(buffer);
372
  return true;
373
}
374
375
#if !JPEGXL_ENABLE_SJPEG
376
377
Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info,
378
                       const std::vector<uint8_t>& icc,
379
                       std::vector<uint8_t> exif, const JpegParams& params,
380
                       std::vector<uint8_t>* bytes) {
381
  return JXL_FAILURE("JPEG XL was built without sjpeg support");
382
}
383
384
#else  //  JPEGXL_ENABLE_SJPEG
385
386
struct MySearchHook : public sjpeg::SearchHook {
387
  uint8_t base_tables[2][64];
388
  float q_start;
389
  float q_precision;
390
  float first_iter_slope;
391
  void ReadBaseTables(const std::string& fn) {
392
    const uint8_t kJPEGAnnexKMatrices[2][64] = {
393
        {16, 11, 10, 16, 24,  40,  51,  61,  12, 12, 14, 19, 26,  58,  60,  55,
394
         14, 13, 16, 24, 40,  57,  69,  56,  14, 17, 22, 29, 51,  87,  80,  62,
395
         18, 22, 37, 56, 68,  109, 103, 77,  24, 35, 55, 64, 81,  104, 113, 92,
396
         49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99},
397
        {17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99,
398
         24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99,
399
         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
400
         99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}};
401
    memcpy(base_tables[0], kJPEGAnnexKMatrices[0], sizeof(base_tables[0]));
402
    memcpy(base_tables[1], kJPEGAnnexKMatrices[1], sizeof(base_tables[1]));
403
    if (!fn.empty()) {
404
      std::ifstream f(fn);
405
      std::string line;
406
      int idx = 0;
407
      while (idx < 128 && std::getline(f, line)) {
408
        if (line.empty() || line[0] == '#') continue;
409
        std::istringstream line_stream(line);
410
        std::string token;
411
        while (idx < 128 && std::getline(line_stream, token, ',')) {
412
          uint8_t val = std::stoi(token);
413
          base_tables[idx / 64][idx % 64] = val;
414
          idx++;
415
        }
416
      }
417
    }
418
  }
419
  bool Setup(const sjpeg::EncoderParam& param) override {
420
    sjpeg::SearchHook::Setup(param);
421
    q = q_start;
422
    return true;
423
  }
424
  void NextMatrix(int idx, uint8_t dst[64]) override {
425
    float factor = (q <= 0)       ? 5000.0f
426
                   : (q < 50.0f)  ? 5000.0f / q
427
                   : (q < 100.0f) ? 2 * (100.0f - q)
428
                                  : 0.0f;
429
    sjpeg::SetQuantMatrix(base_tables[idx], factor, dst);
430
  }
431
  bool Update(float result) override {
432
    value = result;
433
    if (std::fabs(value - target) < tolerance * target) {
434
      return true;
435
    }
436
    if (value > target) {
437
      qmax = q;
438
    } else {
439
      qmin = q;
440
    }
441
    if (qmin == qmax) {
442
      return true;
443
    }
444
    const float last_q = q;
445
    if (pass == 0) {
446
      q += first_iter_slope *
447
           (for_size ? 0.1 * std::log(target / value) : (target - value));
448
      q = std::max(qmin, std::min(qmax, q));
449
    } else {
450
      q = (qmin + qmax) / 2.;
451
    }
452
    return (pass > 0 && std::fabs(q - last_q) < q_precision);
453
  }
454
  ~MySearchHook() override = default;
455
};
456
457
Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info,
458
                       const std::vector<uint8_t>& icc,
459
                       std::vector<uint8_t> exif, const JpegParams& params,
460
                       std::vector<uint8_t>* bytes) {
461
  if (image.format.data_type != JXL_TYPE_UINT8) {
462
    return JXL_FAILURE("Unsupported pixel data type");
463
  }
464
  if (info.alpha_bits > 0) {
465
    return JXL_FAILURE("alpha is not supported");
466
  }
467
  sjpeg::EncoderParam param(params.quality);
468
  if (!icc.empty()) {
469
    param.iccp.assign(icc.begin(), icc.end());
470
  }
471
  if (!exif.empty()) {
472
    ResetExifOrientation(exif);
473
    param.exif.assign(exif.begin(), exif.end());
474
  }
475
  if (params.chroma_subsampling == "444") {
476
    param.yuv_mode = SJPEG_YUV_444;
477
  } else if (params.chroma_subsampling == "420") {
478
    param.yuv_mode = SJPEG_YUV_420;
479
  } else if (params.chroma_subsampling == "420sharp") {
480
    param.yuv_mode = SJPEG_YUV_SHARP;
481
  } else {
482
    return JXL_FAILURE("sjpeg does not support this chroma subsampling mode");
483
  }
484
  param.adaptive_quantization = params.enable_adaptive_quant;
485
  std::unique_ptr<MySearchHook> hook;
486
  if (params.libjpeg_quality > 0) {
487
    JpegParams libjpeg_params;
488
    libjpeg_params.quality = params.libjpeg_quality;
489
    libjpeg_params.chroma_subsampling = params.libjpeg_chroma_subsampling;
490
    std::vector<uint8_t> libjpeg_bytes;
491
    JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(image, info, icc, exif,
492
                                          libjpeg_params, &libjpeg_bytes));
493
    param.target_mode = sjpeg::EncoderParam::TARGET_SIZE;
494
    param.target_value = libjpeg_bytes.size();
495
  }
496
  if (params.psnr_target > 0) {
497
    param.target_mode = sjpeg::EncoderParam::TARGET_PSNR;
498
    param.target_value = params.psnr_target;
499
  }
500
  if (param.target_mode != sjpeg::EncoderParam::TARGET_NONE) {
501
    param.passes = params.search_max_iters;
502
    param.tolerance = params.search_tolerance;
503
    param.qmin = params.search_q_min;
504
    param.qmax = params.search_q_max;
505
    hook = jxl::make_unique<MySearchHook>();
506
    hook->ReadBaseTables(params.custom_base_quant_fn);
507
    hook->q_start = params.search_q_start;
508
    hook->q_precision = params.search_q_precision;
509
    hook->first_iter_slope = params.search_first_iter_slope;
510
    param.search_hook = hook.get();
511
  }
512
  const size_t num_channels = image.format.num_channels;
513
  if (num_channels != 1 && num_channels != 3) {
514
    return JXL_FAILURE("sjpeg supports only grayscale and RGB input");
515
  }
516
  const size_t stride = image.xsize * num_channels;
517
  const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
518
  std::string output;
519
  if (num_channels == 1) {
520
    JXL_RETURN_IF_ERROR(sjpeg::EncodeGray(pixels, image.xsize, image.ysize,
521
                                          stride, param, &output));
522
  } else {
523
    JXL_RETURN_IF_ERROR(sjpeg::Encode(pixels, image.xsize, image.ysize, stride,
524
                                      param, &output));
525
  }
526
  bytes->assign(
527
      reinterpret_cast<const uint8_t*>(output.data()),
528
      reinterpret_cast<const uint8_t*>(output.data() + output.size()));
529
  return true;
530
}
531
532
#endif  // JPEGXL_ENABLE_SJPEG
533
534
Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
535
                      const std::vector<uint8_t>& icc,
536
                      std::vector<uint8_t> exif, JpegEncoder encoder,
537
                      const JpegParams& params, ThreadPool* pool,
538
                      std::vector<uint8_t>* bytes) {
539
  if (params.quality > 100) {
540
    return JXL_FAILURE("please specify a 0-100 JPEG quality");
541
  }
542
543
  switch (encoder) {
544
    case JpegEncoder::kLibJpeg:
545
      JXL_RETURN_IF_ERROR(
546
          EncodeWithLibJpeg(image, info, icc, std::move(exif), params, bytes));
547
      break;
548
    case JpegEncoder::kSJpeg:
549
      JXL_RETURN_IF_ERROR(
550
          EncodeWithSJpeg(image, info, icc, std::move(exif), params, bytes));
551
      break;
552
    default:
553
      return JXL_FAILURE("tried to use an unknown JPEG encoder");
554
  }
555
556
  return true;
557
}
558
559
class JPEGEncoder : public Encoder {
560
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
561
    std::vector<JxlPixelFormat> formats;
562
    for (const uint32_t num_channels : {1, 2, 3, 4}) {
563
      for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
564
        formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
565
                                         /*data_type=*/JXL_TYPE_UINT8,
566
                                         /*endianness=*/endianness,
567
                                         /*align=*/0});
568
      }
569
      formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
570
                                       /*data_type=*/JXL_TYPE_UINT16,
571
                                       /*endianness=*/JXL_BIG_ENDIAN,
572
                                       /*align=*/0});
573
    }
574
    return formats;
575
  }
576
  Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
577
                ThreadPool* pool) const override {
578
    JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
579
    JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg;
580
    JpegParams params;
581
    for (const auto& it : options()) {
582
      if (it.first == "q") {
583
        std::istringstream is(it.second);
584
        JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.quality));
585
      } else if (it.first == "libjpeg_quality") {
586
        std::istringstream is(it.second);
587
        JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.libjpeg_quality));
588
      } else if (it.first == "chroma_subsampling") {
589
        params.chroma_subsampling = it.second;
590
      } else if (it.first == "libjpeg_chroma_subsampling") {
591
        params.libjpeg_chroma_subsampling = it.second;
592
      } else if (it.first == "jpeg_encoder") {
593
        if (it.second == "libjpeg") {
594
          jpeg_encoder = JpegEncoder::kLibJpeg;
595
        } else if (it.second == "sjpeg") {
596
          jpeg_encoder = JpegEncoder::kSJpeg;
597
        } else {
598
          return JXL_FAILURE("unknown jpeg encoder \"%s\"", it.second.c_str());
599
        }
600
      } else if (it.first == "progressive") {
601
        std::istringstream is(it.second);
602
        JXL_RETURN_IF_ERROR(static_cast<bool>(is >> params.progressive_id));
603
      } else if (it.first == "optimize" && it.second == "OFF") {
604
        params.optimize_coding = false;
605
      } else if (it.first == "adaptive_q" && it.second == "OFF") {
606
        params.enable_adaptive_quant = false;
607
      } else if (it.first == "psnr") {
608
        params.psnr_target = std::stof(it.second);
609
      } else if (it.first == "base_quant_fn") {
610
        params.custom_base_quant_fn = it.second;
611
      } else if (it.first == "search_q_start") {
612
        params.search_q_start = std::stof(it.second);
613
      } else if (it.first == "search_q_min") {
614
        params.search_q_min = std::stof(it.second);
615
      } else if (it.first == "search_q_max") {
616
        params.search_q_max = std::stof(it.second);
617
      } else if (it.first == "search_max_iters") {
618
        params.search_max_iters = std::stoi(it.second);
619
      } else if (it.first == "search_tolerance") {
620
        params.search_tolerance = std::stof(it.second);
621
      } else if (it.first == "search_q_precision") {
622
        params.search_q_precision = std::stof(it.second);
623
      } else if (it.first == "search_first_iter_slope") {
624
        params.search_first_iter_slope = std::stof(it.second);
625
      }
626
    }
627
    params.is_xyb = (ppf.color_encoding.color_space == JXL_COLOR_SPACE_XYB);
628
    encoded_image->bitstreams.clear();
629
    encoded_image->bitstreams.reserve(ppf.frames.size());
630
    for (const auto& frame : ppf.frames) {
631
      JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
632
      encoded_image->bitstreams.emplace_back();
633
      JXL_RETURN_IF_ERROR(EncodeImageJPG(
634
          frame.color, ppf.info, ppf.icc, ppf.metadata.exif, jpeg_encoder,
635
          params, pool, &encoded_image->bitstreams.back()));
636
    }
637
    return true;
638
  }
639
};
640
641
}  // namespace
642
643
std::unique_ptr<Encoder> GetJPEGEncoder() {
644
  return jxl::make_unique<JPEGEncoder>();
645
}
646
647
}  // namespace extras
648
}  // namespace jxl
649
650
#endif  // JPEGXL_ENABLE_JPEG