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