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