libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

enc_external_image.cc (9991B)


      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/jxl/enc_external_image.h"
      7 
      8 #include <jxl/types.h>
      9 #include <string.h>
     10 
     11 #include <atomic>
     12 #include <utility>
     13 
     14 #include "lib/jxl/base/byte_order.h"
     15 #include "lib/jxl/base/common.h"
     16 #include "lib/jxl/base/float.h"
     17 #include "lib/jxl/base/printf_macros.h"
     18 
     19 namespace jxl {
     20 namespace {
     21 
     22 size_t JxlDataTypeBytes(JxlDataType data_type) {
     23   switch (data_type) {
     24     case JXL_TYPE_UINT8:
     25       return 1;
     26     case JXL_TYPE_UINT16:
     27       return 2;
     28     case JXL_TYPE_FLOAT16:
     29       return 2;
     30     case JXL_TYPE_FLOAT:
     31       return 4;
     32     default:
     33       return 0;
     34   }
     35 }
     36 
     37 }  // namespace
     38 
     39 Status ConvertFromExternalNoSizeCheck(const uint8_t* data, size_t xsize,
     40                                       size_t ysize, size_t stride,
     41                                       size_t bits_per_sample,
     42                                       JxlPixelFormat format, size_t c,
     43                                       ThreadPool* pool, ImageF* channel) {
     44   if (format.data_type == JXL_TYPE_UINT8) {
     45     JXL_RETURN_IF_ERROR(bits_per_sample > 0 && bits_per_sample <= 8);
     46   } else if (format.data_type == JXL_TYPE_UINT16) {
     47     JXL_RETURN_IF_ERROR(bits_per_sample > 8 && bits_per_sample <= 16);
     48   } else if (format.data_type != JXL_TYPE_FLOAT16 &&
     49              format.data_type != JXL_TYPE_FLOAT) {
     50     JXL_FAILURE("unsupported pixel format data type %d", format.data_type);
     51   }
     52 
     53   JXL_ASSERT(channel->xsize() == xsize);
     54   JXL_ASSERT(channel->ysize() == ysize);
     55 
     56   size_t bytes_per_channel = JxlDataTypeBytes(format.data_type);
     57   size_t bytes_per_pixel = format.num_channels * bytes_per_channel;
     58   size_t pixel_offset = c * bytes_per_channel;
     59   // Only for uint8/16.
     60   float scale = 1.0f;
     61   if (format.data_type == JXL_TYPE_UINT8) {
     62     // We will do an integer multiplication by 257 in LoadFloatRow so that a
     63     // UINT8 value and the corresponding UINT16 value convert to the same float
     64     scale = 1.0f / (257 * ((1ull << bits_per_sample) - 1));
     65   } else {
     66     scale = 1.0f / ((1ull << bits_per_sample) - 1);
     67   }
     68 
     69   const bool little_endian =
     70       format.endianness == JXL_LITTLE_ENDIAN ||
     71       (format.endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
     72 
     73   std::atomic<size_t> error_count = {0};
     74 
     75   const auto convert_row = [&](const uint32_t task, size_t /*thread*/) {
     76     const size_t y = task;
     77     size_t offset = y * stride + pixel_offset;
     78     float* JXL_RESTRICT row_out = channel->Row(y);
     79     const auto save_value = [&](size_t index, float value) {
     80       row_out[index] = value;
     81     };
     82     if (!LoadFloatRow(data + offset, xsize, bytes_per_pixel, format.data_type,
     83                       little_endian, scale, save_value)) {
     84       error_count++;
     85     }
     86   };
     87   JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
     88                                 ThreadPool::NoInit, convert_row,
     89                                 "ConvertExtraChannel"));
     90 
     91   if (error_count) {
     92     JXL_FAILURE("unsupported pixel format data type");
     93   }
     94 
     95   return true;
     96 }
     97 
     98 Status ConvertFromExternalNoSizeCheck(const uint8_t* data, size_t xsize,
     99                                       size_t ysize, size_t stride,
    100                                       const ColorEncoding& c_current,
    101                                       size_t color_channels,
    102                                       size_t bits_per_sample,
    103                                       JxlPixelFormat format, ThreadPool* pool,
    104                                       ImageBundle* ib) {
    105   bool has_alpha = format.num_channels == 2 || format.num_channels == 4;
    106   if (format.num_channels < color_channels) {
    107     return JXL_FAILURE("Expected %" PRIuS
    108                        " color channels, received only %u channels",
    109                        color_channels, format.num_channels);
    110   }
    111 
    112   JXL_ASSIGN_OR_RETURN(Image3F color, Image3F::Create(xsize, ysize));
    113   for (size_t c = 0; c < color_channels; ++c) {
    114     JXL_RETURN_IF_ERROR(ConvertFromExternalNoSizeCheck(
    115         data, xsize, ysize, stride, bits_per_sample, format, c, pool,
    116         &color.Plane(c)));
    117   }
    118   if (color_channels == 1) {
    119     CopyImageTo(color.Plane(0), &color.Plane(1));
    120     CopyImageTo(color.Plane(0), &color.Plane(2));
    121   }
    122   ib->SetFromImage(std::move(color), c_current);
    123 
    124   // Passing an interleaved image with an alpha channel to an image that doesn't
    125   // have alpha channel just discards the passed alpha channel.
    126   if (has_alpha && ib->HasAlpha()) {
    127     JXL_ASSIGN_OR_RETURN(ImageF alpha, ImageF::Create(xsize, ysize));
    128     JXL_RETURN_IF_ERROR(ConvertFromExternalNoSizeCheck(
    129         data, xsize, ysize, stride, bits_per_sample, format,
    130         format.num_channels - 1, pool, &alpha));
    131     ib->SetAlpha(std::move(alpha));
    132   } else if (!has_alpha && ib->HasAlpha()) {
    133     // if alpha is not passed, but it is expected, then assume
    134     // it is all-opaque
    135     JXL_ASSIGN_OR_RETURN(ImageF alpha, ImageF::Create(xsize, ysize));
    136     FillImage(1.0f, &alpha);
    137     ib->SetAlpha(std::move(alpha));
    138   }
    139 
    140   return true;
    141 }
    142 
    143 Status ConvertFromExternal(const uint8_t* data, size_t size, size_t xsize,
    144                            size_t ysize, size_t bits_per_sample,
    145                            JxlPixelFormat format, size_t c, ThreadPool* pool,
    146                            ImageF* channel) {
    147   size_t bytes_per_channel = JxlDataTypeBytes(format.data_type);
    148   size_t bytes_per_pixel = format.num_channels * bytes_per_channel;
    149   const size_t last_row_size = xsize * bytes_per_pixel;
    150   const size_t align = format.align;
    151   const size_t row_size =
    152       (align > 1 ? jxl::DivCeil(last_row_size, align) * align : last_row_size);
    153   const size_t bytes_to_read = row_size * (ysize - 1) + last_row_size;
    154   if (xsize == 0 || ysize == 0) return JXL_FAILURE("Empty image");
    155   if (size > 0 && size < bytes_to_read) {
    156     return JXL_FAILURE("Buffer size is too small, expected: %" PRIuS
    157                        " got: %" PRIuS " (Image: %" PRIuS "x%" PRIuS
    158                        "x%u, bytes_per_channel: %" PRIuS ")",
    159                        bytes_to_read, size, xsize, ysize, format.num_channels,
    160                        bytes_per_channel);
    161   }
    162   // Too large buffer is likely an application bug, so also fail for that.
    163   // Do allow padding to stride in last row though.
    164   if (size > row_size * ysize) {
    165     return JXL_FAILURE("Buffer size is too large");
    166   }
    167   return ConvertFromExternalNoSizeCheck(
    168       data, xsize, ysize, row_size, bits_per_sample, format, c, pool, channel);
    169 }
    170 Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
    171                            size_t ysize, const ColorEncoding& c_current,
    172                            size_t color_channels, size_t bits_per_sample,
    173                            JxlPixelFormat format, ThreadPool* pool,
    174                            ImageBundle* ib) {
    175   bool has_alpha = format.num_channels == 2 || format.num_channels == 4;
    176   if (format.num_channels < color_channels) {
    177     return JXL_FAILURE("Expected %" PRIuS
    178                        " color channels, received only %u channels",
    179                        color_channels, format.num_channels);
    180   }
    181 
    182   JXL_ASSIGN_OR_RETURN(Image3F color, Image3F::Create(xsize, ysize));
    183   for (size_t c = 0; c < color_channels; ++c) {
    184     JXL_RETURN_IF_ERROR(ConvertFromExternal(bytes.data(), bytes.size(), xsize,
    185                                             ysize, bits_per_sample, format, c,
    186                                             pool, &color.Plane(c)));
    187   }
    188   if (color_channels == 1) {
    189     CopyImageTo(color.Plane(0), &color.Plane(1));
    190     CopyImageTo(color.Plane(0), &color.Plane(2));
    191   }
    192   ib->SetFromImage(std::move(color), c_current);
    193 
    194   // Passing an interleaved image with an alpha channel to an image that doesn't
    195   // have alpha channel just discards the passed alpha channel.
    196   if (has_alpha && ib->HasAlpha()) {
    197     JXL_ASSIGN_OR_RETURN(ImageF alpha, ImageF::Create(xsize, ysize));
    198     JXL_RETURN_IF_ERROR(ConvertFromExternal(
    199         bytes.data(), bytes.size(), xsize, ysize, bits_per_sample, format,
    200         format.num_channels - 1, pool, &alpha));
    201     ib->SetAlpha(std::move(alpha));
    202   } else if (!has_alpha && ib->HasAlpha()) {
    203     // if alpha is not passed, but it is expected, then assume
    204     // it is all-opaque
    205     JXL_ASSIGN_OR_RETURN(ImageF alpha, ImageF::Create(xsize, ysize));
    206     FillImage(1.0f, &alpha);
    207     ib->SetAlpha(std::move(alpha));
    208   }
    209 
    210   return true;
    211 }
    212 
    213 Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
    214                            size_t ysize, const ColorEncoding& c_current,
    215                            size_t bits_per_sample, JxlPixelFormat format,
    216                            ThreadPool* pool, ImageBundle* ib) {
    217   return ConvertFromExternal(bytes, xsize, ysize, c_current,
    218                              c_current.Channels(), bits_per_sample, format,
    219                              pool, ib);
    220 }
    221 
    222 Status BufferToImageF(const JxlPixelFormat& pixel_format, size_t xsize,
    223                       size_t ysize, const void* buffer, size_t size,
    224                       ThreadPool* pool, ImageF* channel) {
    225   size_t bitdepth = JxlDataTypeBytes(pixel_format.data_type) * kBitsPerByte;
    226   return ConvertFromExternal(reinterpret_cast<const uint8_t*>(buffer), size,
    227                              xsize, ysize, bitdepth, pixel_format, 0, pool,
    228                              channel);
    229 }
    230 
    231 Status BufferToImageBundle(const JxlPixelFormat& pixel_format, uint32_t xsize,
    232                            uint32_t ysize, const void* buffer, size_t size,
    233                            jxl::ThreadPool* pool,
    234                            const jxl::ColorEncoding& c_current,
    235                            jxl::ImageBundle* ib) {
    236   size_t bitdepth = JxlDataTypeBytes(pixel_format.data_type) * kBitsPerByte;
    237   JXL_RETURN_IF_ERROR(ConvertFromExternal(
    238       jxl::Bytes(static_cast<const uint8_t*>(buffer), size), xsize, ysize,
    239       c_current, bitdepth, pixel_format, pool, ib));
    240   ib->VerifyMetadata();
    241 
    242   return true;
    243 }
    244 
    245 }  // namespace jxl