2 * Copyright (c) 2024 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/internal/imaging/common/loader-jpeg.h>
23 #include <libexif/exif-data.h>
24 #include <libexif/exif-loader.h>
25 #include <libexif/exif-tag.h>
27 #include <turbojpeg.h>
34 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
35 #include <dali/public-api/object/property-array.h>
36 #include <dali/public-api/object/property-map.h>
39 #include <dali/devel-api/adaptor-framework/environment-variable.h>
40 #include <dali/devel-api/adaptor-framework/image-loading.h>
41 #include <dali/internal/imaging/common/image-operations.h>
42 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
43 #include <dali/internal/legacy/tizen/platform-capabilities.h>
48 namespace Pixel = Dali::Pixel;
49 using PixelArray = uint8_t*;
50 const unsigned int DECODED_L8 = 1;
51 const unsigned int DECODED_RGB888 = 3;
52 const unsigned int DECODED_RGBA8888 = 4;
54 const char* CHROMINANCE_SUBSAMPLING_OPTIONS_ENV[] = {"DALI_ENABLE_DECODE_JPEG_TO_YUV_444",
55 "DALI_ENABLE_DECODE_JPEG_TO_YUV_422",
56 "DALI_ENABLE_DECODE_JPEG_TO_YUV_420",
58 "DALI_ENABLE_DECODE_JPEG_TO_YUV_440",
59 "DALI_ENABLE_DECODE_JPEG_TO_YUV_411",
60 "DALI_ENABLE_DECODE_JPEG_TO_YUV_441"};
62 const int CHROMINANCE_SUBSAMPLING_OPTIONS_ENV_COUNT = sizeof(CHROMINANCE_SUBSAMPLING_OPTIONS_ENV) / sizeof(CHROMINANCE_SUBSAMPLING_OPTIONS_ENV[0]);
64 static bool gSubsamplingFormatTable[TJ_NUMSAMP] = {
67 static bool gIsSubsamplingFormatTableInitialized = false;
69 /** Transformations that can be applied to decoded pixels to respect exif orientation
70 * codes in image headers */
71 enum class JpegTransform
73 NONE, //< no transformation 0th-Row = top & 0th-Column = left
74 FLIP_HORIZONTAL, //< horizontal flip 0th-Row = top & 0th-Column = right
75 ROTATE_180, //< 180-degree rotation 0th-Row = bottom & 0th-Column = right
76 FLIP_VERTICAL, //< vertical flip 0th-Row = bottom & 0th-Column = left
77 TRANSPOSE, //< transpose across UL-to-LR axis 0th-Row = left & 0th-Column = top
78 ROTATE_90, //< 90-degree clockwise rotation 0th-Row = right & 0th-Column = top
79 TRANSVERSE, //< transpose across UR-to-LL axis 0th-Row = right & 0th-Column = bottom
80 ROTATE_270, //< 270-degree clockwise (or 90 ccw) 0th-Row = left & 0th-Column = bottom
84 * @brief Error handling bookeeping for the JPEG Turbo library's
85 * setjmp/longjmp simulated exceptions.
89 struct jpeg_error_mgr errorManager;
93 static bool IsSubsamplingFormatEnabled(int chrominanceSubsampling)
95 if(!gIsSubsamplingFormatTableInitialized)
97 for(int i = 0; i < TJ_NUMSAMP; i++)
99 const char* envName = DALI_LIKELY(i < CHROMINANCE_SUBSAMPLING_OPTIONS_ENV_COUNT) ? CHROMINANCE_SUBSAMPLING_OPTIONS_ENV[i] : "";
100 auto valueString = Dali::EnvironmentVariable::GetEnvironmentVariable(envName);
101 gSubsamplingFormatTable[i] = valueString ? std::atoi(valueString) : false;
104 gIsSubsamplingFormatTableInitialized = true;
107 if(DALI_UNLIKELY(chrominanceSubsampling >= TJ_NUMSAMP))
109 DALI_LOG_WARNING("WARNING! Input subsampling value [%d] is bigger than turbojpeg library support [%d]\n", chrominanceSubsampling, TJ_NUMSAMP);
112 return chrominanceSubsampling < TJ_NUMSAMP ? gSubsamplingFormatTable[chrominanceSubsampling] : false;
116 * @brief Called by the JPEG library when it hits an error.
117 * We jump out of the library so our loader code can return an error.
119 void JpegErrorHandler(j_common_ptr cinfo)
121 DALI_LOG_ERROR("JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n");
122 /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
123 JpegErrorState* myerr = reinterpret_cast<JpegErrorState*>(cinfo->err);
125 /* Return control to the setjmp point */
126 longjmp(myerr->jumpBuffer, 1);
129 void JpegOutputMessageHandler(j_common_ptr cinfo)
131 /* Stop libjpeg from printing to stderr - Do Nothing */
135 * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
136 * the JPEG to be displayed and fatal errors.
138 bool IsJpegErrorFatal(const std::string& errorMessage)
140 if((errorMessage.find("Corrupt JPEG data") != std::string::npos) ||
141 (errorMessage.find("Invalid SOS parameters") != std::string::npos) ||
142 (errorMessage.find("Invalid JPEG file structure") != std::string::npos) ||
143 (errorMessage.find("Unsupported JPEG process") != std::string::npos) ||
144 (errorMessage.find("Unsupported marker type") != std::string::npos) ||
145 (errorMessage.find("Bogus marker length") != std::string::npos) ||
146 (errorMessage.find("Bogus DQT index") != std::string::npos) ||
147 (errorMessage.find("Bogus Huffman table definition") != std::string::npos))
154 bool IsJpegDecodingFailed()
156 std::string errorString = tjGetErrorStr();
158 if(DALI_UNLIKELY(IsJpegErrorFatal(errorString)))
160 DALI_LOG_ERROR("%s\n", errorString.c_str());
165 DALI_LOG_WARNING("%s\n", errorString.c_str());
170 // helpers for safe exif memory handling
171 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
173 ExifHandle MakeNullExifData()
175 return ExifHandle{nullptr, exif_data_free};
178 ExifHandle MakeExifDataFromData(uint8_t* data, unsigned int size)
180 return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
183 // Helpers for safe Jpeg memory handling
184 using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
186 JpegHandle MakeJpegCompressor()
188 return JpegHandle{tjInitCompress(), tjDestroy};
191 JpegHandle MakeJpegDecompressor()
193 return JpegHandle{tjInitDecompress(), tjDestroy};
196 using JpegMemoryHandle = std::unique_ptr<uint8_t, decltype(tjFree)*>;
198 JpegMemoryHandle MakeJpegMemory()
200 return JpegMemoryHandle{nullptr, tjFree};
203 template<class T, class Deleter>
204 class UniquePointerSetter final
207 UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
208 : mUniquePointer(uniquePointer),
213 /// @brief Pointer to Pointer cast operator
219 /// @brief Destructor, reset the unique_ptr
220 ~UniquePointerSetter()
222 mUniquePointer.reset(mRawPointer);
226 std::unique_ptr<T, Deleter>& mUniquePointer;
230 template<typename T, typename Deleter>
231 UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
233 return UniquePointerSetter<T, Deleter>{uniquePointer};
236 using TransformFunction = std::function<void(PixelArray, unsigned, unsigned)>;
237 using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
239 /// @brief Select the transform function depending on the pixel format
240 TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
241 Pixel::Format pixelFormat)
243 auto function = TransformFunction{};
245 int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
246 switch(decodedPixelSize)
250 function = functions[0];
255 function = functions[1];
258 case DECODED_RGBA8888:
260 function = functions[2];
265 DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
266 function = functions[1];
273 // Storing Exif fields as properties
274 template<class R, class V>
275 R ConvertExifNumeric(const ExifEntry& entry)
277 return static_cast<R>((*reinterpret_cast<V*>(entry.data)));
280 void AddExifFieldPropertyMap(Dali::Property::Map& out, const ExifEntry& entry, ExifIfd ifd)
282 auto shortName = std::string(exif_tag_get_name_in_ifd(entry.tag, ifd));
285 case EXIF_FORMAT_ASCII:
287 out.Insert(shortName, std::string(reinterpret_cast<char*>(entry.data), entry.size));
290 case EXIF_FORMAT_SHORT:
292 out.Insert(shortName, ConvertExifNumeric<int, uint16_t>(entry));
295 case EXIF_FORMAT_LONG:
297 out.Insert(shortName, ConvertExifNumeric<int, uint32_t>(entry));
300 case EXIF_FORMAT_SSHORT:
302 out.Insert(shortName, ConvertExifNumeric<int, int16_t>(entry));
305 case EXIF_FORMAT_SLONG:
307 out.Insert(shortName, ConvertExifNumeric<int, int32_t>(entry));
310 case EXIF_FORMAT_FLOAT:
312 out.Insert(shortName, ConvertExifNumeric<float, float>(entry));
315 case EXIF_FORMAT_DOUBLE:
317 out.Insert(shortName, ConvertExifNumeric<float, double>(entry));
320 case EXIF_FORMAT_RATIONAL:
322 auto values = reinterpret_cast<unsigned int*>(entry.data);
323 Dali::Property::Array array;
324 array.Add(static_cast<int>(values[0]));
325 array.Add(static_cast<int>(values[1]));
326 out.Insert(shortName, array);
329 case EXIF_FORMAT_SBYTE:
331 out.Insert(shortName, "EXIF_FORMAT_SBYTE Unsupported");
334 case EXIF_FORMAT_BYTE:
336 out.Insert(shortName, "EXIF_FORMAT_BYTE Unsupported");
339 case EXIF_FORMAT_SRATIONAL:
341 auto values = reinterpret_cast<int*>(entry.data);
342 Dali::Property::Array array;
343 array.Add(values[0]);
344 array.Add(values[1]);
345 out.Insert(shortName, array);
348 case EXIF_FORMAT_UNDEFINED:
351 std::stringstream ss;
352 ss << "EXIF_FORMAT_UNDEFINED, size: " << entry.size << ", components: " << entry.components;
353 out.Insert(shortName, ss.str());
358 /// @brief Apply a transform to a buffer
359 bool Transform(const TransformFunctionArray& transformFunctions,
363 Pixel::Format pixelFormat)
365 auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
366 if(transformFunction)
368 transformFunction(buffer, width, height);
370 return bool(transformFunction);
373 /// @brief Auxiliar type to represent pixel data with different number of bytes
381 void Rotate180(PixelArray buffer, int width, int height)
383 // Destination pixel, set as the first pixel of screen
384 auto to = reinterpret_cast<PixelType<N>*>(buffer);
386 // Source pixel, as the image is flipped horizontally and vertically,
387 // the source pixel is the end of the buffer of size width * height
388 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
390 for(auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
392 std::swap(*from, *to);
397 void FlipHorizontal(PixelArray buffer, int width, int height)
399 for(auto iy = 0; iy < height; ++iy)
401 //Set the destination pixel as the beginning of the row
402 auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
403 //Set the source pixel as the end of the row to flip in X axis
404 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
405 for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
407 std::swap(*from, *to);
413 void FlipVertical(PixelArray buffer, int width, int height)
415 //Transform vertically only
416 for(auto iy = 0; iy < height / 2; ++iy)
418 for(auto ix = 0; ix < width; ++ix)
420 auto to = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
421 auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
422 std::swap(*from, *to);
428 void Transpose(PixelArray buffer, int width, int height)
430 using PixelT = PixelType<N>;
432 data.ResizeUninitialized(width * height);
433 auto dataPtr = data.Begin();
435 auto original = reinterpret_cast<PixelT*>(buffer);
436 std::copy(original, original + width * height, dataPtr);
439 for(auto iy = 0; iy < width; ++iy)
441 for(auto ix = 0; ix < height; ++ix, ++to)
443 auto from = dataPtr + ix * width + iy;
450 void Rotate90(PixelArray buffer, int width, int height)
452 using PixelT = PixelType<N>;
454 data.ResizeUninitialized(width * height);
455 auto dataPtr = data.Begin();
457 auto original = reinterpret_cast<PixelT*>(buffer);
458 std::copy(original, original + width * height, dataPtr);
460 std::swap(width, height);
461 auto hw = width * height;
464 auto to = original + width - 1;
467 for(auto ix = width; --ix >= 0;)
469 for(auto iy = height; --iy >= 0; ++from)
479 void Transverse(PixelArray buffer, int width, int height)
481 using PixelT = PixelType<N>;
483 data.ResizeUninitialized(width * height);
484 auto dataPtr = data.Begin();
486 auto original = reinterpret_cast<PixelT*>(buffer);
487 std::copy(original, original + width * height, dataPtr);
490 for(auto iy = 0; iy < width; iy++)
492 for(auto ix = 0; ix < height; ix++)
494 auto from = dataPtr + (height - ix) * width - 1 - iy;
502 void Rotate270(PixelArray buffer, int width, int height)
504 using PixelT = PixelType<N>;
506 data.ResizeUninitialized(width * height);
507 auto dataPtr = data.Begin();
509 auto original = reinterpret_cast<PixelT*>(buffer);
510 std::copy(original, original + width * height, dataPtr);
513 std::swap(width, height);
514 auto hw = width * height;
516 auto* to = original + hw - width;
517 auto* from = dataPtr;
521 for(auto ix = width; --ix >= 0;)
523 for(auto iy = height; --iy >= 0;)
533 void GetJpegPixelFormat(int jpegColorspace, TJPF& pixelLibJpegType, Pixel::Format& pixelFormat)
535 pixelLibJpegType = TJPF_RGB;
536 pixelFormat = Pixel::RGB888;
538 switch(jpegColorspace)
541 // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
542 // YCbCr images must be converted to RGB before they can actually be displayed.
545 pixelLibJpegType = TJPF_RGB;
546 pixelFormat = Pixel::RGB888;
551 pixelLibJpegType = TJPF_GRAY;
552 pixelFormat = Pixel::L8;
558 pixelLibJpegType = TJPF_CMYK;
559 pixelFormat = Pixel::RGB888;
564 pixelLibJpegType = TJPF_RGB;
565 pixelFormat = Pixel::RGB888;
571 bool TransformBitmap(int scaledPreXformWidth, int scaledPreXformHeight, JpegTransform transform, uint8_t* bitmapPixelBuffer, Pixel::Format pixelFormat)
573 const unsigned int bufferWidth = Dali::TizenPlatform::GetTextureDimension(scaledPreXformWidth);
574 const unsigned int bufferHeight = Dali::TizenPlatform::GetTextureDimension(scaledPreXformHeight);
580 case JpegTransform::NONE:
585 // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
586 case JpegTransform::ROTATE_180:
588 static auto rotate180Functions = TransformFunctionArray{
593 result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
596 case JpegTransform::ROTATE_270:
598 static auto rotate270Functions = TransformFunctionArray{
603 result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
606 case JpegTransform::ROTATE_90:
608 static auto rotate90Functions = TransformFunctionArray{
613 result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
616 case JpegTransform::FLIP_VERTICAL:
618 static auto flipVerticalFunctions = TransformFunctionArray{
623 result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
626 // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
627 case JpegTransform::FLIP_HORIZONTAL:
629 static auto flipHorizontalFunctions = TransformFunctionArray{
634 result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
637 case JpegTransform::TRANSPOSE:
639 static auto transposeFunctions = TransformFunctionArray{
644 result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
647 case JpegTransform::TRANSVERSE:
649 static auto transverseFunctions = TransformFunctionArray{
654 result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
659 DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
666 bool LoadJpegFile(const Dali::ImageLoader::Input& input, Vector<uint8_t>& jpegBuffer, unsigned int& jpegBufferSize)
668 FILE* const fp = input.file;
670 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END)))
672 DALI_LOG_ERROR("Error seeking to end of file\n");
676 long positionIndicator = ftell(fp);
678 if(positionIndicator > -1L)
680 jpegBufferSize = static_cast<unsigned int>(positionIndicator);
683 if(DALI_UNLIKELY(0u == jpegBufferSize))
685 DALI_LOG_ERROR("Jpeg buffer size error\n");
689 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
691 DALI_LOG_ERROR("Error seeking to start of file\n");
697 jpegBuffer.ResizeUninitialized(jpegBufferSize);
701 DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
704 uint8_t* const jpegBufferPtr = jpegBuffer.Begin();
706 // Pull the compressed JPEG image bytes out of a file and into memory:
707 if(DALI_UNLIKELY(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize))
709 DALI_LOG_ERROR("Error on image file read.\n");
713 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
715 DALI_LOG_ERROR("Error seeking to start of file\n");
723 * @brief Helper function to convert from Turbo Jpeg Pixel Format as TJPF_CMYK to RGB888 by naive method.
725 * @param[in] cmykBuffer buffer of cmyk.
726 * @param[in] rgbBuffer buffer of Pixel::RGB888
727 * @param[in] width width of image.
728 * @param[in] height height of image.
730 void ConvertTjpfCMYKToRGB888(PixelArray __restrict__ cmykBuffer, PixelArray __restrict__ rgbBuffer, int32_t width, int32_t height)
732 const int32_t pixelCount = width * height;
733 const uint8_t cmykBpp = 4u;
734 const uint8_t bpp = 3u;
736 const PixelArray cmykBufferEnd = cmykBuffer + pixelCount * cmykBpp;
737 // Convert every pixel
738 while(cmykBuffer != cmykBufferEnd)
740 const uint8_t channelK = *(cmykBuffer + 3u);
741 *(rgbBuffer + 0u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 0u), channelK);
742 *(rgbBuffer + 1u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 1u), channelK);
743 *(rgbBuffer + 2u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 2u), channelK);
744 cmykBuffer += cmykBpp;
752 namespace TizenPlatform
754 bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv);
755 JpegTransform ConvertExifOrientation(ExifData* exifData);
756 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight);
758 bool LoadJpegHeader(FILE* fp, unsigned int& width, unsigned int& height)
760 // using libjpeg API to avoid having to read the whole file in a buffer
761 struct jpeg_decompress_struct cinfo;
762 struct JpegErrorState jerr;
763 cinfo.err = jpeg_std_error(&jerr.errorManager);
765 jerr.errorManager.output_message = JpegOutputMessageHandler;
766 jerr.errorManager.error_exit = JpegErrorHandler;
768 // On error exit from the JPEG lib, control will pass via JpegErrorHandler
769 // into this branch body for cleanup and error return:
770 if(DALI_UNLIKELY(setjmp(jerr.jumpBuffer)))
772 DALI_LOG_ERROR("setjmp failed\n");
773 jpeg_destroy_decompress(&cinfo);
777 // jpeg_create_decompress internally uses C casts
778 #pragma GCC diagnostic push
779 #pragma GCC diagnostic ignored "-Wold-style-cast"
780 jpeg_create_decompress(&cinfo);
781 #pragma GCC diagnostic pop
783 jpeg_stdio_src(&cinfo, fp);
785 // Check header to see if it is JPEG file
786 if(DALI_UNLIKELY(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK))
788 DALI_LOG_ERROR("jpeg_read_header failed\n");
790 jpeg_destroy_decompress(&cinfo);
794 width = cinfo.image_width;
795 height = cinfo.image_height;
797 jpeg_destroy_decompress(&cinfo);
801 bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
803 std::vector<Dali::Devel::PixelBuffer> pixelBuffers;
805 bool result = DecodeJpeg(input, pixelBuffers, false);
806 if(!result && pixelBuffers.empty())
812 bitmap = pixelBuffers[0];
817 bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
819 return DecodeJpeg(input, pixelBuffers, true);
822 bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv)
824 Vector<uint8_t> jpegBuffer;
825 unsigned int jpegBufferSize = 0u;
827 if(!LoadJpegFile(input, jpegBuffer, jpegBufferSize))
829 DALI_LOG_ERROR("LoadJpegFile failed\n");
833 auto jpeg = MakeJpegDecompressor();
834 if(DALI_UNLIKELY(!jpeg))
836 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
840 uint8_t* const jpegBufferPtr = jpegBuffer.Begin();
841 auto transform = JpegTransform::NONE;
844 auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
846 if(exifData && input.reorientationRequested)
848 transform = ConvertExifOrientation(exifData.get());
851 // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
852 int chrominanceSubsampling = -1;
853 int preXformImageWidth = 0, preXformImageHeight = 0;
854 int jpegColorspace = -1;
856 if(tjDecompressHeader3(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace) == -1)
858 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
859 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
862 if(DALI_UNLIKELY(preXformImageWidth == 0 || preXformImageHeight == 0))
864 DALI_LOG_ERROR("Invalid Image!\n");
868 int requiredWidth = input.scalingParameters.dimensions.GetWidth();
869 int requiredHeight = input.scalingParameters.dimensions.GetHeight();
871 // If transform is a 90 or 270 degree rotation, the logical width and height
872 // request from the client needs to be adjusted to account by effectively
873 // rotating that too, and the final width and height need to be swapped:
874 int postXformImageWidth = preXformImageWidth;
875 int postXformImageHeight = preXformImageHeight;
877 int scaledPreXformWidth = preXformImageWidth;
878 int scaledPreXformHeight = preXformImageHeight;
879 int scaledPostXformWidth = postXformImageWidth;
880 int scaledPostXformHeight = postXformImageHeight;
882 TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
886 // Check decoding format
887 if(decodeToYuv && IsSubsamplingFormatEnabled(chrominanceSubsampling) && transform == JpegTransform::NONE)
891 // Allocate buffers for each plane and decompress the jpeg buffer into the buffers
892 for(int i = 0; i < 3; i++)
894 auto planeSize = tjPlaneSizeYUV(i, scaledPostXformWidth, 0, scaledPostXformHeight, chrominanceSubsampling);
896 uint8_t* buffer = static_cast<uint8_t*>(malloc(planeSize));
897 if(DALI_UNLIKELY(!buffer))
899 DALI_LOG_ERROR("Buffer allocation is failed [%d]\n", planeSize);
900 pixelBuffers.clear();
904 int width, height, planeWidth;
905 Pixel::Format pixelFormat = Pixel::RGB888;
910 width = scaledPostXformWidth;
911 height = scaledPostXformHeight;
912 planeWidth = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
913 pixelFormat = Pixel::L8;
918 width = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
919 height = tjPlaneHeight(i, scaledPostXformHeight, chrominanceSubsampling);
921 pixelFormat = (i == 1 ? Pixel::CHROMINANCE_U : Pixel::CHROMINANCE_V);
924 Internal::Adaptor::PixelBufferPtr internal = Internal::Adaptor::PixelBuffer::New(buffer, planeSize, width, height, planeWidth, pixelFormat);
925 Dali::Devel::PixelBuffer bitmap = Devel::PixelBuffer(internal.Get());
927 pixelBuffers.push_back(bitmap);
932 int decodeResult = tjDecompressToYUVPlanes(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t**>(&planes), scaledPostXformWidth, nullptr, scaledPostXformHeight, flags);
933 if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
935 pixelBuffers.clear();
943 // Colorspace conversion options
944 TJPF pixelLibJpegType = TJPF_RGB;
945 Pixel::Format pixelFormat = Pixel::RGB888;
947 GetJpegPixelFormat(jpegColorspace, pixelLibJpegType, pixelFormat);
949 // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
950 Dali::Devel::PixelBuffer bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
953 if(DALI_LIKELY(exifData))
955 std::unique_ptr<Property::Map> exifMap = std::make_unique<Property::Map>();
957 for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
959 auto content = exifData->ifd[k];
960 for(auto i = 0u; i < content->count; ++i)
962 auto&& tag = content->entries[i];
963 const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
966 AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
971 GetImplementation(bitmap).SetMetadata(std::move(exifMap));
974 auto bitmapPixelBuffer = bitmap.GetBuffer();
977 int decodeResult = -1;
978 if(pixelLibJpegType == TJPF_CMYK)
980 // Currently we support only for 4 bytes per each CMYK pixel.
981 const uint8_t cmykBytesPerPixel = 4u;
983 uint8_t* cmykBuffer = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * scaledPostXformWidth * scaledPostXformHeight * cmykBytesPerPixel));
985 if(DALI_UNLIKELY(!cmykBuffer))
987 DALI_LOG_ERROR("cmykBuffer allocation is failed [%zu]\n", sizeof(uint8_t) * scaledPostXformWidth * scaledPostXformHeight * cmykBytesPerPixel);
991 decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(cmykBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
992 if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
997 ConvertTjpfCMYKToRGB888(cmykBuffer, bitmapPixelBuffer, scaledPostXformWidth, scaledPostXformHeight);
1003 decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
1004 if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
1009 pixelBuffers.push_back(bitmap);
1012 result = TransformBitmap(scaledPreXformWidth, scaledPreXformHeight, transform, bitmapPixelBuffer, pixelFormat);
1018 bool EncodeToJpeg(const uint8_t* const pixelBuffer, Vector<uint8_t>& encodedPixels, const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality)
1020 if(DALI_UNLIKELY(!pixelBuffer))
1022 DALI_LOG_ERROR("Null input buffer\n");
1026 // Translate pixel format enum:
1027 int jpegPixelFormat = -1;
1033 jpegPixelFormat = TJPF_GRAY;
1038 jpegPixelFormat = TJPF_RGB;
1041 case Pixel::RGBA8888:
1043 // Ignore the alpha:
1044 jpegPixelFormat = TJPF_RGBX;
1047 case Pixel::BGRA8888:
1049 // Ignore the alpha:
1050 jpegPixelFormat = TJPF_BGRX;
1055 DALI_LOG_ERROR("Unsupported pixel format for encoding to JPEG. Format enum : [%d]\n", pixelFormat);
1060 // Assert quality is in the documented allowable range of the jpeg-turbo lib:
1061 DALI_ASSERT_DEBUG(quality >= 1);
1062 DALI_ASSERT_DEBUG(quality <= 100);
1072 // Initialise a JPEG codec:
1074 auto jpeg = MakeJpegCompressor();
1075 if(DALI_UNLIKELY(!jpeg))
1077 DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
1081 // Safely wrap the jpeg codec's buffer in case we are about to throw, then
1082 // save the pixels to a persistent buffer that we own and let our cleaner
1083 // class clean up the buffer as it goes out of scope:
1084 auto dstBuffer = MakeJpegMemory();
1086 // Run the compressor:
1087 unsigned long dstBufferSize = 0;
1088 const int flags = 0;
1090 if(DALI_UNLIKELY(tjCompress2(jpeg.get(),
1091 const_cast<uint8_t*>(pixelBuffer),
1096 SetPointer(dstBuffer),
1102 DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
1106 encodedPixels.ResizeUninitialized(dstBufferSize);
1107 memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
1112 JpegTransform ConvertExifOrientation(ExifData* exifData)
1114 auto transform = JpegTransform::NONE;
1115 ExifEntry* const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
1116 int orientation = 0;
1119 orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
1124 transform = JpegTransform::NONE;
1129 transform = JpegTransform::FLIP_HORIZONTAL;
1134 transform = JpegTransform::ROTATE_180;
1139 transform = JpegTransform::FLIP_VERTICAL;
1144 transform = JpegTransform::TRANSPOSE;
1149 transform = JpegTransform::ROTATE_90;
1154 transform = JpegTransform::TRANSVERSE;
1159 transform = JpegTransform::ROTATE_270;
1164 // Try to keep loading the file, but let app developer know there was something fishy:
1165 DALI_LOG_WARNING("Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry);
1173 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight)
1175 bool success = true;
1176 if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1178 std::swap(requiredWidth, requiredHeight);
1179 std::swap(postXformImageWidth, postXformImageHeight);
1182 // Apply the special rules for when there are one or two zeros in requested dimensions:
1183 const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions(ImageDimensions(postXformImageWidth, postXformImageHeight), ImageDimensions(requiredWidth, requiredHeight));
1184 requiredWidth = correctedDesired.GetWidth();
1185 requiredHeight = correctedDesired.GetHeight();
1187 // Rescale image during decode using one of the decoder's built-in rescaling
1188 // ratios (expected to be powers of 2), keeping the final image at least as
1189 // wide and high as was requested:
1192 tjscalingfactor* factors = tjGetScalingFactors(&numFactors);
1193 if(DALI_UNLIKELY(factors == NULL))
1195 DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
1200 // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
1201 // apply it if the application requested one of those:
1202 // (use a switch case here so this code will fail to compile if other modes are added)
1203 bool useTurboJpegScaleFactor = true;
1204 switch(samplingMode)
1206 case SamplingMode::BOX:
1207 case SamplingMode::BOX_THEN_NEAREST:
1208 case SamplingMode::BOX_THEN_LINEAR:
1209 case SamplingMode::DONT_CARE:
1211 useTurboJpegScaleFactor = true;
1214 case SamplingMode::NO_FILTER:
1215 case SamplingMode::NEAREST:
1216 case SamplingMode::LINEAR:
1218 useTurboJpegScaleFactor = false;
1223 int scaleFactorIndex(-1);
1224 int fittedScaledWidth(postXformImageWidth);
1225 int fittedScaledHeight(postXformImageHeight);
1226 if(useTurboJpegScaleFactor)
1228 // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
1229 for(int i = 0; i < numFactors; ++i)
1231 int scaledWidth = TJSCALED(postXformImageWidth, factors[i]);
1232 int scaledHeight = TJSCALED(postXformImageHeight, factors[i]);
1233 bool widthLessRequired = scaledWidth < requiredWidth;
1234 bool heightLessRequired = scaledHeight < requiredHeight;
1235 // If either scaled dimension is smaller than the desired one, we were done at the last iteration
1236 if((fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired))
1240 // If both dimensions are smaller than the desired one, we were done at the last iteration:
1241 if((fittingMode == FittingMode::SHRINK_TO_FIT) && (widthLessRequired && heightLessRequired))
1245 // If the width is smaller than the desired one, we were done at the last iteration:
1246 if(fittingMode == FittingMode::FIT_WIDTH && widthLessRequired)
1250 // If the width is smaller than the desired one, we were done at the last iteration:
1251 if(fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired)
1255 // This factor stays is within our fitting mode constraint so remember it:
1256 scaleFactorIndex = i;
1257 fittedScaledWidth = scaledWidth;
1258 fittedScaledHeight = scaledHeight;
1262 const int maxTextureSize = static_cast<int>(Dali::GetMaxTextureSize());
1264 // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
1265 if(fittedScaledWidth >= maxTextureSize ||
1266 fittedScaledHeight >= maxTextureSize)
1268 for(int i = scaleFactorIndex + 1; i < numFactors; ++i)
1270 // Continue downscaling to below maximum texture size (if possible)
1271 scaleFactorIndex = i;
1272 fittedScaledWidth = TJSCALED(postXformImageWidth, factors[i]);
1273 fittedScaledHeight = TJSCALED(postXformImageHeight, factors[i]);
1275 if(fittedScaledWidth < maxTextureSize &&
1276 fittedScaledHeight < maxTextureSize)
1278 // Current scale-factor downscales to below maximum texture size
1284 // We have finally chosen the scale-factor, return width/height values
1285 if(scaleFactorIndex >= 0 && scaleFactorIndex < numFactors)
1287 preXformImageWidth = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
1288 preXformImageHeight = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
1289 postXformImageWidth = fittedScaledWidth;
1290 postXformImageHeight = fittedScaledHeight;
1297 ExifHandle LoadExifData(FILE* fp)
1299 auto exifData = MakeNullExifData();
1300 uint8_t dataBuffer[1024];
1302 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
1304 DALI_LOG_ERROR("Error seeking to start of file\n");
1308 auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
1309 exif_loader_new(), exif_loader_unref};
1313 int size = fread(dataBuffer, 1, sizeof(dataBuffer), fp);
1318 if(!exif_loader_write(exifLoader.get(), dataBuffer, size))
1324 exifData.reset(exif_loader_get_data(exifLoader.get()));
1330 bool LoadJpegHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
1332 unsigned int requiredWidth = input.scalingParameters.dimensions.GetWidth();
1333 unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1334 FILE* const fp = input.file;
1336 bool success = false;
1338 unsigned int headerWidth;
1339 unsigned int headerHeight;
1341 success = LoadJpegHeader(fp, headerWidth, headerHeight);
1342 if(DALI_LIKELY(success))
1344 auto transform = JpegTransform::NONE;
1346 if(input.reorientationRequested)
1348 auto exifData = LoadExifData(fp);
1351 transform = ConvertExifOrientation(exifData.get());
1355 if(requiredWidth == 0 && requiredHeight == 0)
1357 if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1359 std::swap(headerWidth, headerHeight);
1364 int preXformImageWidth = headerWidth;
1365 int preXformImageHeight = headerHeight;
1366 int postXformImageWidth = headerWidth;
1367 int postXformImageHeight = headerHeight;
1369 success = TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
1370 if(DALI_LIKELY(success))
1372 headerWidth = postXformImageWidth;
1373 headerHeight = postXformImageHeight;
1376 width = headerWidth;
1377 height = headerHeight;
1383 } // namespace TizenPlatform