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 static bool gSubsamplingFormatTable[TJ_NUMSAMP] = {
65 static bool gIsSubsamplingFormatTableInitialized = false;
67 /** Transformations that can be applied to decoded pixels to respect exif orientation
68 * codes in image headers */
69 enum class JpegTransform
71 NONE, //< no transformation 0th-Row = top & 0th-Column = left
72 FLIP_HORIZONTAL, //< horizontal flip 0th-Row = top & 0th-Column = right
73 ROTATE_180, //< 180-degree rotation 0th-Row = bottom & 0th-Column = right
74 FLIP_VERTICAL, //< vertical flip 0th-Row = bottom & 0th-Column = left
75 TRANSPOSE, //< transpose across UL-to-LR axis 0th-Row = left & 0th-Column = top
76 ROTATE_90, //< 90-degree clockwise rotation 0th-Row = right & 0th-Column = top
77 TRANSVERSE, //< transpose across UR-to-LL axis 0th-Row = right & 0th-Column = bottom
78 ROTATE_270, //< 270-degree clockwise (or 90 ccw) 0th-Row = left & 0th-Column = bottom
82 * @brief Error handling bookeeping for the JPEG Turbo library's
83 * setjmp/longjmp simulated exceptions.
87 struct jpeg_error_mgr errorManager;
91 static bool IsSubsamplingFormatEnabled(int chrominanceSubsampling)
93 if(!gIsSubsamplingFormatTableInitialized)
95 for(int i = 0; i < TJ_NUMSAMP; i++)
97 auto valueString = Dali::EnvironmentVariable::GetEnvironmentVariable(CHROMINANCE_SUBSAMPLING_OPTIONS_ENV[i]);
98 gSubsamplingFormatTable[i] = valueString ? std::atoi(valueString) : false;
101 gIsSubsamplingFormatTableInitialized = true;
104 return gSubsamplingFormatTable[chrominanceSubsampling];
108 * @brief Called by the JPEG library when it hits an error.
109 * We jump out of the library so our loader code can return an error.
111 void JpegErrorHandler(j_common_ptr cinfo)
113 DALI_LOG_ERROR("JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n");
114 /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
115 JpegErrorState* myerr = reinterpret_cast<JpegErrorState*>(cinfo->err);
117 /* Return control to the setjmp point */
118 longjmp(myerr->jumpBuffer, 1);
121 void JpegOutputMessageHandler(j_common_ptr cinfo)
123 /* Stop libjpeg from printing to stderr - Do Nothing */
127 * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
128 * the JPEG to be displayed and fatal errors.
130 bool IsJpegErrorFatal(const std::string& errorMessage)
132 if((errorMessage.find("Corrupt JPEG data") != std::string::npos) ||
133 (errorMessage.find("Invalid SOS parameters") != std::string::npos) ||
134 (errorMessage.find("Invalid JPEG file structure") != std::string::npos) ||
135 (errorMessage.find("Unsupported JPEG process") != std::string::npos) ||
136 (errorMessage.find("Unsupported marker type") != std::string::npos) ||
137 (errorMessage.find("Bogus marker length") != std::string::npos) ||
138 (errorMessage.find("Bogus DQT index") != std::string::npos) ||
139 (errorMessage.find("Bogus Huffman table definition") != std::string::npos))
146 bool IsJpegDecodingFailed()
148 std::string errorString = tjGetErrorStr();
150 if(DALI_UNLIKELY(IsJpegErrorFatal(errorString)))
152 DALI_LOG_ERROR("%s\n", errorString.c_str());
157 DALI_LOG_WARNING("%s\n", errorString.c_str());
162 // helpers for safe exif memory handling
163 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
165 ExifHandle MakeNullExifData()
167 return ExifHandle{nullptr, exif_data_free};
170 ExifHandle MakeExifDataFromData(uint8_t* data, unsigned int size)
172 return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
175 // Helpers for safe Jpeg memory handling
176 using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
178 JpegHandle MakeJpegCompressor()
180 return JpegHandle{tjInitCompress(), tjDestroy};
183 JpegHandle MakeJpegDecompressor()
185 return JpegHandle{tjInitDecompress(), tjDestroy};
188 using JpegMemoryHandle = std::unique_ptr<uint8_t, decltype(tjFree)*>;
190 JpegMemoryHandle MakeJpegMemory()
192 return JpegMemoryHandle{nullptr, tjFree};
195 template<class T, class Deleter>
196 class UniquePointerSetter final
199 UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
200 : mUniquePointer(uniquePointer),
205 /// @brief Pointer to Pointer cast operator
211 /// @brief Destructor, reset the unique_ptr
212 ~UniquePointerSetter()
214 mUniquePointer.reset(mRawPointer);
218 std::unique_ptr<T, Deleter>& mUniquePointer;
222 template<typename T, typename Deleter>
223 UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
225 return UniquePointerSetter<T, Deleter>{uniquePointer};
228 using TransformFunction = std::function<void(PixelArray, unsigned, unsigned)>;
229 using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
231 /// @brief Select the transform function depending on the pixel format
232 TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
233 Pixel::Format pixelFormat)
235 auto function = TransformFunction{};
237 int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
238 switch(decodedPixelSize)
242 function = functions[0];
247 function = functions[1];
250 case DECODED_RGBA8888:
252 function = functions[2];
257 DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
258 function = functions[1];
265 // Storing Exif fields as properties
266 template<class R, class V>
267 R ConvertExifNumeric(const ExifEntry& entry)
269 return static_cast<R>((*reinterpret_cast<V*>(entry.data)));
272 void AddExifFieldPropertyMap(Dali::Property::Map& out, const ExifEntry& entry, ExifIfd ifd)
274 auto shortName = std::string(exif_tag_get_name_in_ifd(entry.tag, ifd));
277 case EXIF_FORMAT_ASCII:
279 out.Insert(shortName, std::string(reinterpret_cast<char*>(entry.data), entry.size));
282 case EXIF_FORMAT_SHORT:
284 out.Insert(shortName, ConvertExifNumeric<int, uint16_t>(entry));
287 case EXIF_FORMAT_LONG:
289 out.Insert(shortName, ConvertExifNumeric<int, uint32_t>(entry));
292 case EXIF_FORMAT_SSHORT:
294 out.Insert(shortName, ConvertExifNumeric<int, int16_t>(entry));
297 case EXIF_FORMAT_SLONG:
299 out.Insert(shortName, ConvertExifNumeric<int, int32_t>(entry));
302 case EXIF_FORMAT_FLOAT:
304 out.Insert(shortName, ConvertExifNumeric<float, float>(entry));
307 case EXIF_FORMAT_DOUBLE:
309 out.Insert(shortName, ConvertExifNumeric<float, double>(entry));
312 case EXIF_FORMAT_RATIONAL:
314 auto values = reinterpret_cast<unsigned int*>(entry.data);
315 Dali::Property::Array array;
316 array.Add(static_cast<int>(values[0]));
317 array.Add(static_cast<int>(values[1]));
318 out.Insert(shortName, array);
321 case EXIF_FORMAT_SBYTE:
323 out.Insert(shortName, "EXIF_FORMAT_SBYTE Unsupported");
326 case EXIF_FORMAT_BYTE:
328 out.Insert(shortName, "EXIF_FORMAT_BYTE Unsupported");
331 case EXIF_FORMAT_SRATIONAL:
333 auto values = reinterpret_cast<int*>(entry.data);
334 Dali::Property::Array array;
335 array.Add(values[0]);
336 array.Add(values[1]);
337 out.Insert(shortName, array);
340 case EXIF_FORMAT_UNDEFINED:
343 std::stringstream ss;
344 ss << "EXIF_FORMAT_UNDEFINED, size: " << entry.size << ", components: " << entry.components;
345 out.Insert(shortName, ss.str());
350 /// @brief Apply a transform to a buffer
351 bool Transform(const TransformFunctionArray& transformFunctions,
355 Pixel::Format pixelFormat)
357 auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
358 if(transformFunction)
360 transformFunction(buffer, width, height);
362 return bool(transformFunction);
365 /// @brief Auxiliar type to represent pixel data with different number of bytes
373 void Rotate180(PixelArray buffer, int width, int height)
375 // Destination pixel, set as the first pixel of screen
376 auto to = reinterpret_cast<PixelType<N>*>(buffer);
378 // Source pixel, as the image is flipped horizontally and vertically,
379 // the source pixel is the end of the buffer of size width * height
380 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
382 for(auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
384 std::swap(*from, *to);
389 void FlipHorizontal(PixelArray buffer, int width, int height)
391 for(auto iy = 0; iy < height; ++iy)
393 //Set the destination pixel as the beginning of the row
394 auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
395 //Set the source pixel as the end of the row to flip in X axis
396 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
397 for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
399 std::swap(*from, *to);
405 void FlipVertical(PixelArray buffer, int width, int height)
407 //Transform vertically only
408 for(auto iy = 0; iy < height / 2; ++iy)
410 for(auto ix = 0; ix < width; ++ix)
412 auto to = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
413 auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
414 std::swap(*from, *to);
420 void Transpose(PixelArray buffer, int width, int height)
422 using PixelT = PixelType<N>;
424 data.ResizeUninitialized(width * height);
425 auto dataPtr = data.Begin();
427 auto original = reinterpret_cast<PixelT*>(buffer);
428 std::copy(original, original + width * height, dataPtr);
431 for(auto iy = 0; iy < width; ++iy)
433 for(auto ix = 0; ix < height; ++ix, ++to)
435 auto from = dataPtr + ix * width + iy;
442 void Rotate90(PixelArray buffer, int width, int height)
444 using PixelT = PixelType<N>;
446 data.ResizeUninitialized(width * height);
447 auto dataPtr = data.Begin();
449 auto original = reinterpret_cast<PixelT*>(buffer);
450 std::copy(original, original + width * height, dataPtr);
452 std::swap(width, height);
453 auto hw = width * height;
456 auto to = original + width - 1;
459 for(auto ix = width; --ix >= 0;)
461 for(auto iy = height; --iy >= 0; ++from)
471 void Transverse(PixelArray buffer, int width, int height)
473 using PixelT = PixelType<N>;
475 data.ResizeUninitialized(width * height);
476 auto dataPtr = data.Begin();
478 auto original = reinterpret_cast<PixelT*>(buffer);
479 std::copy(original, original + width * height, dataPtr);
482 for(auto iy = 0; iy < width; iy++)
484 for(auto ix = 0; ix < height; ix++)
486 auto from = dataPtr + (height - ix) * width - 1 - iy;
494 void Rotate270(PixelArray buffer, int width, int height)
496 using PixelT = PixelType<N>;
498 data.ResizeUninitialized(width * height);
499 auto dataPtr = data.Begin();
501 auto original = reinterpret_cast<PixelT*>(buffer);
502 std::copy(original, original + width * height, dataPtr);
505 std::swap(width, height);
506 auto hw = width * height;
508 auto* to = original + hw - width;
509 auto* from = dataPtr;
513 for(auto ix = width; --ix >= 0;)
515 for(auto iy = height; --iy >= 0;)
525 void GetJpegPixelFormat(int jpegColorspace, TJPF& pixelLibJpegType, Pixel::Format& pixelFormat)
527 pixelLibJpegType = TJPF_RGB;
528 pixelFormat = Pixel::RGB888;
530 switch(jpegColorspace)
533 // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
534 // YCbCr images must be converted to RGB before they can actually be displayed.
537 pixelLibJpegType = TJPF_RGB;
538 pixelFormat = Pixel::RGB888;
543 pixelLibJpegType = TJPF_GRAY;
544 pixelFormat = Pixel::L8;
550 pixelLibJpegType = TJPF_CMYK;
551 pixelFormat = Pixel::RGB888;
556 pixelLibJpegType = TJPF_RGB;
557 pixelFormat = Pixel::RGB888;
563 bool TransformBitmap(int scaledPreXformWidth, int scaledPreXformHeight, JpegTransform transform, uint8_t* bitmapPixelBuffer, Pixel::Format pixelFormat)
565 const unsigned int bufferWidth = Dali::TizenPlatform::GetTextureDimension(scaledPreXformWidth);
566 const unsigned int bufferHeight = Dali::TizenPlatform::GetTextureDimension(scaledPreXformHeight);
572 case JpegTransform::NONE:
577 // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
578 case JpegTransform::ROTATE_180:
580 static auto rotate180Functions = TransformFunctionArray{
585 result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
588 case JpegTransform::ROTATE_270:
590 static auto rotate270Functions = TransformFunctionArray{
595 result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
598 case JpegTransform::ROTATE_90:
600 static auto rotate90Functions = TransformFunctionArray{
605 result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
608 case JpegTransform::FLIP_VERTICAL:
610 static auto flipVerticalFunctions = TransformFunctionArray{
615 result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
618 // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
619 case JpegTransform::FLIP_HORIZONTAL:
621 static auto flipHorizontalFunctions = TransformFunctionArray{
626 result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
629 case JpegTransform::TRANSPOSE:
631 static auto transposeFunctions = TransformFunctionArray{
636 result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
639 case JpegTransform::TRANSVERSE:
641 static auto transverseFunctions = TransformFunctionArray{
646 result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
651 DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
658 bool LoadJpegFile(const Dali::ImageLoader::Input& input, Vector<uint8_t>& jpegBuffer, unsigned int& jpegBufferSize)
660 FILE* const fp = input.file;
662 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END)))
664 DALI_LOG_ERROR("Error seeking to end of file\n");
668 long positionIndicator = ftell(fp);
670 if(positionIndicator > -1L)
672 jpegBufferSize = static_cast<unsigned int>(positionIndicator);
675 if(DALI_UNLIKELY(0u == jpegBufferSize))
677 DALI_LOG_ERROR("Jpeg buffer size error\n");
681 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
683 DALI_LOG_ERROR("Error seeking to start of file\n");
689 jpegBuffer.ResizeUninitialized(jpegBufferSize);
693 DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
696 uint8_t* const jpegBufferPtr = jpegBuffer.Begin();
698 // Pull the compressed JPEG image bytes out of a file and into memory:
699 if(DALI_UNLIKELY(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize))
701 DALI_LOG_ERROR("Error on image file read.\n");
705 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
707 DALI_LOG_ERROR("Error seeking to start of file\n");
715 * @brief Helper function to convert from Turbo Jpeg Pixel Format as TJPF_CMYK to RGB888 by naive method.
717 * @param[in] cmykBuffer buffer of cmyk.
718 * @param[in] rgbBuffer buffer of Pixel::RGB888
719 * @param[in] width width of image.
720 * @param[in] height height of image.
722 void ConvertTjpfCMYKToRGB888(PixelArray __restrict__ cmykBuffer, PixelArray __restrict__ rgbBuffer, int32_t width, int32_t height)
724 const int32_t pixelCount = width * height;
725 const uint8_t cmykBpp = 4u;
726 const uint8_t bpp = 3u;
728 const PixelArray cmykBufferEnd = cmykBuffer + pixelCount * cmykBpp;
729 // Convert every pixel
730 while(cmykBuffer != cmykBufferEnd)
732 const uint8_t channelK = *(cmykBuffer + 3u);
733 *(rgbBuffer + 0u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 0u), channelK);
734 *(rgbBuffer + 1u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 1u), channelK);
735 *(rgbBuffer + 2u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 2u), channelK);
736 cmykBuffer += cmykBpp;
744 namespace TizenPlatform
746 bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv);
747 JpegTransform ConvertExifOrientation(ExifData* exifData);
748 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight);
750 bool LoadJpegHeader(FILE* fp, unsigned int& width, unsigned int& height)
752 // using libjpeg API to avoid having to read the whole file in a buffer
753 struct jpeg_decompress_struct cinfo;
754 struct JpegErrorState jerr;
755 cinfo.err = jpeg_std_error(&jerr.errorManager);
757 jerr.errorManager.output_message = JpegOutputMessageHandler;
758 jerr.errorManager.error_exit = JpegErrorHandler;
760 // On error exit from the JPEG lib, control will pass via JpegErrorHandler
761 // into this branch body for cleanup and error return:
762 if(DALI_UNLIKELY(setjmp(jerr.jumpBuffer)))
764 DALI_LOG_ERROR("setjmp failed\n");
765 jpeg_destroy_decompress(&cinfo);
769 // jpeg_create_decompress internally uses C casts
770 #pragma GCC diagnostic push
771 #pragma GCC diagnostic ignored "-Wold-style-cast"
772 jpeg_create_decompress(&cinfo);
773 #pragma GCC diagnostic pop
775 jpeg_stdio_src(&cinfo, fp);
777 // Check header to see if it is JPEG file
778 if(DALI_UNLIKELY(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK))
780 DALI_LOG_ERROR("jpeg_read_header failed\n");
782 jpeg_destroy_decompress(&cinfo);
786 width = cinfo.image_width;
787 height = cinfo.image_height;
789 jpeg_destroy_decompress(&cinfo);
793 bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
795 std::vector<Dali::Devel::PixelBuffer> pixelBuffers;
797 bool result = DecodeJpeg(input, pixelBuffers, false);
798 if(!result && pixelBuffers.empty())
804 bitmap = pixelBuffers[0];
809 bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
811 return DecodeJpeg(input, pixelBuffers, true);
814 bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv)
816 Vector<uint8_t> jpegBuffer;
817 unsigned int jpegBufferSize = 0u;
819 if(!LoadJpegFile(input, jpegBuffer, jpegBufferSize))
821 DALI_LOG_ERROR("LoadJpegFile failed\n");
825 auto jpeg = MakeJpegDecompressor();
826 if(DALI_UNLIKELY(!jpeg))
828 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
832 uint8_t* const jpegBufferPtr = jpegBuffer.Begin();
833 auto transform = JpegTransform::NONE;
836 auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
838 if(exifData && input.reorientationRequested)
840 transform = ConvertExifOrientation(exifData.get());
843 // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
844 int chrominanceSubsampling = -1;
845 int preXformImageWidth = 0, preXformImageHeight = 0;
846 int jpegColorspace = -1;
848 if(tjDecompressHeader3(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace) == -1)
850 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
851 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
854 if(DALI_UNLIKELY(preXformImageWidth == 0 || preXformImageHeight == 0))
856 DALI_LOG_ERROR("Invalid Image!\n");
860 int requiredWidth = input.scalingParameters.dimensions.GetWidth();
861 int requiredHeight = input.scalingParameters.dimensions.GetHeight();
863 // If transform is a 90 or 270 degree rotation, the logical width and height
864 // request from the client needs to be adjusted to account by effectively
865 // rotating that too, and the final width and height need to be swapped:
866 int postXformImageWidth = preXformImageWidth;
867 int postXformImageHeight = preXformImageHeight;
869 int scaledPreXformWidth = preXformImageWidth;
870 int scaledPreXformHeight = preXformImageHeight;
871 int scaledPostXformWidth = postXformImageWidth;
872 int scaledPostXformHeight = postXformImageHeight;
874 TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
878 // Check decoding format
879 if(decodeToYuv && IsSubsamplingFormatEnabled(chrominanceSubsampling) && transform == JpegTransform::NONE)
883 // Allocate buffers for each plane and decompress the jpeg buffer into the buffers
884 for(int i = 0; i < 3; i++)
886 auto planeSize = tjPlaneSizeYUV(i, scaledPostXformWidth, 0, scaledPostXformHeight, chrominanceSubsampling);
888 uint8_t* buffer = static_cast<uint8_t*>(malloc(planeSize));
891 DALI_LOG_ERROR("Buffer allocation is failed [%d]\n", planeSize);
892 pixelBuffers.clear();
896 int width, height, planeWidth;
897 Pixel::Format pixelFormat = Pixel::RGB888;
902 width = scaledPostXformWidth;
903 height = scaledPostXformHeight;
904 planeWidth = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
905 pixelFormat = Pixel::L8;
910 width = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
911 height = tjPlaneHeight(i, scaledPostXformHeight, chrominanceSubsampling);
913 pixelFormat = (i == 1 ? Pixel::CHROMINANCE_U : Pixel::CHROMINANCE_V);
916 Internal::Adaptor::PixelBufferPtr internal = Internal::Adaptor::PixelBuffer::New(buffer, planeSize, width, height, planeWidth, pixelFormat);
917 Dali::Devel::PixelBuffer bitmap = Devel::PixelBuffer(internal.Get());
919 pixelBuffers.push_back(bitmap);
924 int decodeResult = tjDecompressToYUVPlanes(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t**>(&planes), scaledPostXformWidth, nullptr, scaledPostXformHeight, flags);
925 if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
927 pixelBuffers.clear();
935 // Colorspace conversion options
936 TJPF pixelLibJpegType = TJPF_RGB;
937 Pixel::Format pixelFormat = Pixel::RGB888;
939 GetJpegPixelFormat(jpegColorspace, pixelLibJpegType, pixelFormat);
941 // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
942 Dali::Devel::PixelBuffer bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
945 if(DALI_LIKELY(exifData))
947 std::unique_ptr<Property::Map> exifMap = std::make_unique<Property::Map>();
949 for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
951 auto content = exifData->ifd[k];
952 for(auto i = 0u; i < content->count; ++i)
954 auto&& tag = content->entries[i];
955 const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
958 AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
963 GetImplementation(bitmap).SetMetadata(std::move(exifMap));
966 auto bitmapPixelBuffer = bitmap.GetBuffer();
969 int decodeResult = -1;
970 if(pixelLibJpegType == TJPF_CMYK)
972 // Currently we support only for 4 bytes per each CMYK pixel.
973 const uint8_t cmykBytesPerPixel = 4u;
975 uint8_t* cmykBuffer = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * scaledPostXformWidth * scaledPostXformHeight * cmykBytesPerPixel));
977 decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(cmykBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
978 if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
983 ConvertTjpfCMYKToRGB888(cmykBuffer, bitmapPixelBuffer, scaledPostXformWidth, scaledPostXformHeight);
989 decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
990 if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
995 pixelBuffers.push_back(bitmap);
998 result = TransformBitmap(scaledPreXformWidth, scaledPreXformHeight, transform, bitmapPixelBuffer, pixelFormat);
1004 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)
1006 if(DALI_UNLIKELY(!pixelBuffer))
1008 DALI_LOG_ERROR("Null input buffer\n");
1012 // Translate pixel format enum:
1013 int jpegPixelFormat = -1;
1019 jpegPixelFormat = TJPF_GRAY;
1024 jpegPixelFormat = TJPF_RGB;
1027 case Pixel::RGBA8888:
1029 // Ignore the alpha:
1030 jpegPixelFormat = TJPF_RGBX;
1033 case Pixel::BGRA8888:
1035 // Ignore the alpha:
1036 jpegPixelFormat = TJPF_BGRX;
1041 DALI_LOG_ERROR("Unsupported pixel format for encoding to JPEG. Format enum : [%d]\n", pixelFormat);
1046 // Assert quality is in the documented allowable range of the jpeg-turbo lib:
1047 DALI_ASSERT_DEBUG(quality >= 1);
1048 DALI_ASSERT_DEBUG(quality <= 100);
1058 // Initialise a JPEG codec:
1060 auto jpeg = MakeJpegCompressor();
1061 if(DALI_UNLIKELY(!jpeg))
1063 DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
1067 // Safely wrap the jpeg codec's buffer in case we are about to throw, then
1068 // save the pixels to a persistent buffer that we own and let our cleaner
1069 // class clean up the buffer as it goes out of scope:
1070 auto dstBuffer = MakeJpegMemory();
1072 // Run the compressor:
1073 unsigned long dstBufferSize = 0;
1074 const int flags = 0;
1076 if(DALI_UNLIKELY(tjCompress2(jpeg.get(),
1077 const_cast<uint8_t*>(pixelBuffer),
1082 SetPointer(dstBuffer),
1088 DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
1092 encodedPixels.ResizeUninitialized(dstBufferSize);
1093 memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
1098 JpegTransform ConvertExifOrientation(ExifData* exifData)
1100 auto transform = JpegTransform::NONE;
1101 ExifEntry* const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
1102 int orientation = 0;
1105 orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
1110 transform = JpegTransform::NONE;
1115 transform = JpegTransform::FLIP_HORIZONTAL;
1120 transform = JpegTransform::ROTATE_180;
1125 transform = JpegTransform::FLIP_VERTICAL;
1130 transform = JpegTransform::TRANSPOSE;
1135 transform = JpegTransform::ROTATE_90;
1140 transform = JpegTransform::TRANSVERSE;
1145 transform = JpegTransform::ROTATE_270;
1150 // Try to keep loading the file, but let app developer know there was something fishy:
1151 DALI_LOG_WARNING("Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry);
1159 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight)
1161 bool success = true;
1162 if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1164 std::swap(requiredWidth, requiredHeight);
1165 std::swap(postXformImageWidth, postXformImageHeight);
1168 // Apply the special rules for when there are one or two zeros in requested dimensions:
1169 const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions(ImageDimensions(postXformImageWidth, postXformImageHeight), ImageDimensions(requiredWidth, requiredHeight));
1170 requiredWidth = correctedDesired.GetWidth();
1171 requiredHeight = correctedDesired.GetHeight();
1173 // Rescale image during decode using one of the decoder's built-in rescaling
1174 // ratios (expected to be powers of 2), keeping the final image at least as
1175 // wide and high as was requested:
1178 tjscalingfactor* factors = tjGetScalingFactors(&numFactors);
1179 if(DALI_UNLIKELY(factors == NULL))
1181 DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
1186 // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
1187 // apply it if the application requested one of those:
1188 // (use a switch case here so this code will fail to compile if other modes are added)
1189 bool downscale = true;
1190 switch(samplingMode)
1192 case SamplingMode::BOX:
1193 case SamplingMode::BOX_THEN_NEAREST:
1194 case SamplingMode::BOX_THEN_LINEAR:
1195 case SamplingMode::DONT_CARE:
1200 case SamplingMode::NO_FILTER:
1201 case SamplingMode::NEAREST:
1202 case SamplingMode::LINEAR:
1209 int scaleFactorIndex(0);
1212 // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
1213 for(int i = 1; i < numFactors; ++i)
1215 bool widthLessRequired = TJSCALED(postXformImageWidth, factors[i]) < requiredWidth;
1216 bool heightLessRequired = TJSCALED(postXformImageHeight, factors[i]) < requiredHeight;
1217 // If either scaled dimension is smaller than the desired one, we were done at the last iteration
1218 if((fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired))
1222 // If both dimensions are smaller than the desired one, we were done at the last iteration:
1223 if((fittingMode == FittingMode::SHRINK_TO_FIT) && (widthLessRequired && heightLessRequired))
1227 // If the width is smaller than the desired one, we were done at the last iteration:
1228 if(fittingMode == FittingMode::FIT_WIDTH && widthLessRequired)
1232 // If the width is smaller than the desired one, we were done at the last iteration:
1233 if(fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired)
1237 // This factor stays is within our fitting mode constraint so remember it:
1238 scaleFactorIndex = i;
1242 // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
1243 for(int i = scaleFactorIndex; i < numFactors; ++i)
1245 // Continue downscaling to below maximum texture size (if possible)
1246 scaleFactorIndex = i;
1248 if(TJSCALED(postXformImageWidth, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()) &&
1249 TJSCALED(postXformImageHeight, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()))
1251 // Current scale-factor downscales to below maximum texture size
1256 // We have finally chosen the scale-factor, return width/height values
1257 if(scaleFactorIndex > 0)
1259 preXformImageWidth = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
1260 preXformImageHeight = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
1261 postXformImageWidth = TJSCALED(postXformImageWidth, (factors[scaleFactorIndex]));
1262 postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
1269 ExifHandle LoadExifData(FILE* fp)
1271 auto exifData = MakeNullExifData();
1272 uint8_t dataBuffer[1024];
1274 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
1276 DALI_LOG_ERROR("Error seeking to start of file\n");
1280 auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
1281 exif_loader_new(), exif_loader_unref};
1285 int size = fread(dataBuffer, 1, sizeof(dataBuffer), fp);
1290 if(!exif_loader_write(exifLoader.get(), dataBuffer, size))
1296 exifData.reset(exif_loader_get_data(exifLoader.get()));
1302 bool LoadJpegHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
1304 unsigned int requiredWidth = input.scalingParameters.dimensions.GetWidth();
1305 unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1306 FILE* const fp = input.file;
1308 bool success = false;
1310 unsigned int headerWidth;
1311 unsigned int headerHeight;
1313 success = LoadJpegHeader(fp, headerWidth, headerHeight);
1314 if(DALI_LIKELY(success))
1316 auto transform = JpegTransform::NONE;
1318 if(input.reorientationRequested)
1320 auto exifData = LoadExifData(fp);
1323 transform = ConvertExifOrientation(exifData.get());
1327 if(requiredWidth == 0 && requiredHeight == 0)
1329 if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1331 std::swap(headerWidth, headerHeight);
1336 int preXformImageWidth = headerWidth;
1337 int preXformImageHeight = headerHeight;
1338 int postXformImageWidth = headerWidth;
1339 int postXformImageHeight = headerHeight;
1341 success = TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
1342 if(DALI_LIKELY(success))
1344 headerWidth = postXformImageWidth;
1345 headerHeight = postXformImageHeight;
1348 width = headerWidth;
1349 height = headerHeight;
1355 } // namespace TizenPlatform