2 * Copyright (c) 2022 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>
28 #include <turbojpeg.h>
35 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
36 #include <dali/public-api/object/property-array.h>
37 #include <dali/public-api/object/property-map.h>
40 #include <dali/devel-api/adaptor-framework/environment-variable.h>
41 #include <dali/devel-api/adaptor-framework/image-loading.h>
42 #include <dali/internal/imaging/common/image-operations.h>
43 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
44 #include <dali/internal/legacy/tizen/platform-capabilities.h>
49 namespace Pixel = Dali::Pixel;
50 using PixelArray = unsigned char*;
51 const unsigned int DECODED_L8 = 1;
52 const unsigned int DECODED_RGB888 = 3;
53 const unsigned int DECODED_RGBA8888 = 4;
55 constexpr auto DECODE_JPEG_TO_YUV_ENV = "DALI_DECODE_JPEG_TO_YUV_ENV";
57 /** Transformations that can be applied to decoded pixels to respect exif orientation
58 * codes in image headers */
59 enum class JpegTransform
61 NONE, //< no transformation 0th-Row = top & 0th-Column = left
62 FLIP_HORIZONTAL, //< horizontal flip 0th-Row = top & 0th-Column = right
63 ROTATE_180, //< 180-degree rotation 0th-Row = bottom & 0th-Column = right
64 FLIP_VERTICAL, //< vertical flip 0th-Row = bottom & 0th-Column = left
65 TRANSPOSE, //< transpose across UL-to-LR axis 0th-Row = left & 0th-Column = top
66 ROTATE_90, //< 90-degree clockwise rotation 0th-Row = right & 0th-Column = top
67 TRANSVERSE, //< transpose across UR-to-LL axis 0th-Row = right & 0th-Column = bottom
68 ROTATE_270, //< 270-degree clockwise (or 90 ccw) 0th-Row = left & 0th-Column = bottom
72 * @brief Error handling bookeeping for the JPEG Turbo library's
73 * setjmp/longjmp simulated exceptions.
77 struct jpeg_error_mgr errorManager;
82 * @brief Called by the JPEG library when it hits an error.
83 * We jump out of the library so our loader code can return an error.
85 void JpegErrorHandler(j_common_ptr cinfo)
87 DALI_LOG_ERROR("JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n");
88 /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
89 JpegErrorState* myerr = reinterpret_cast<JpegErrorState*>(cinfo->err);
91 /* Return control to the setjmp point */
92 longjmp(myerr->jumpBuffer, 1);
95 void JpegOutputMessageHandler(j_common_ptr cinfo)
97 /* Stop libjpeg from printing to stderr - Do Nothing */
101 * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
102 * the JPEG to be displayed and fatal errors.
104 bool IsJpegErrorFatal(const std::string& errorMessage)
106 if((errorMessage.find("Corrupt JPEG data") != std::string::npos) ||
107 (errorMessage.find("Invalid SOS parameters") != std::string::npos) ||
108 (errorMessage.find("Invalid JPEG file structure") != std::string::npos) ||
109 (errorMessage.find("Unsupported JPEG process") != std::string::npos) ||
110 (errorMessage.find("Unsupported marker type") != std::string::npos) ||
111 (errorMessage.find("Bogus marker length") != std::string::npos) ||
112 (errorMessage.find("Bogus DQT index") != std::string::npos) ||
113 (errorMessage.find("Bogus Huffman table definition") != std::string::npos))
120 // helpers for safe exif memory handling
121 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
123 ExifHandle MakeNullExifData()
125 return ExifHandle{nullptr, exif_data_free};
128 ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size)
130 return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
133 // Helpers for safe Jpeg memory handling
134 using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
136 JpegHandle MakeJpegCompressor()
138 return JpegHandle{tjInitCompress(), tjDestroy};
141 JpegHandle MakeJpegDecompressor()
143 return JpegHandle{tjInitDecompress(), tjDestroy};
146 using JpegMemoryHandle = std::unique_ptr<unsigned char, decltype(tjFree)*>;
148 JpegMemoryHandle MakeJpegMemory()
150 return JpegMemoryHandle{nullptr, tjFree};
153 template<class T, class Deleter>
154 class UniquePointerSetter final
157 UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
158 : mUniquePointer(uniquePointer),
163 /// @brief Pointer to Pointer cast operator
169 /// @brief Destructor, reset the unique_ptr
170 ~UniquePointerSetter()
172 mUniquePointer.reset(mRawPointer);
176 std::unique_ptr<T, Deleter>& mUniquePointer;
180 template<typename T, typename Deleter>
181 UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
183 return UniquePointerSetter<T, Deleter>{uniquePointer};
186 using TransformFunction = std::function<void(PixelArray, unsigned, unsigned)>;
187 using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
189 /// @brief Select the transform function depending on the pixel format
190 TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
191 Pixel::Format pixelFormat)
193 auto function = TransformFunction{};
195 int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
196 switch(decodedPixelSize)
200 function = functions[0];
205 function = functions[1];
208 case DECODED_RGBA8888:
210 function = functions[2];
215 DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
216 function = functions[1];
223 // Storing Exif fields as properties
224 template<class R, class V>
225 R ConvertExifNumeric(const ExifEntry& entry)
227 return static_cast<R>((*reinterpret_cast<V*>(entry.data)));
230 void AddExifFieldPropertyMap(Dali::Property::Map& out, const ExifEntry& entry, ExifIfd ifd)
232 auto shortName = std::string(exif_tag_get_name_in_ifd(entry.tag, ifd));
235 case EXIF_FORMAT_ASCII:
237 out.Insert(shortName, std::string(reinterpret_cast<char*>(entry.data), entry.size));
240 case EXIF_FORMAT_SHORT:
242 out.Insert(shortName, ConvertExifNumeric<int, uint16_t>(entry));
245 case EXIF_FORMAT_LONG:
247 out.Insert(shortName, ConvertExifNumeric<int, uint32_t>(entry));
250 case EXIF_FORMAT_SSHORT:
252 out.Insert(shortName, ConvertExifNumeric<int, int16_t>(entry));
255 case EXIF_FORMAT_SLONG:
257 out.Insert(shortName, ConvertExifNumeric<int, int32_t>(entry));
260 case EXIF_FORMAT_FLOAT:
262 out.Insert(shortName, ConvertExifNumeric<float, float>(entry));
265 case EXIF_FORMAT_DOUBLE:
267 out.Insert(shortName, ConvertExifNumeric<float, double>(entry));
270 case EXIF_FORMAT_RATIONAL:
272 auto values = reinterpret_cast<unsigned int*>(entry.data);
273 Dali::Property::Array array;
274 array.Add(static_cast<int>(values[0]));
275 array.Add(static_cast<int>(values[1]));
276 out.Insert(shortName, array);
279 case EXIF_FORMAT_SBYTE:
281 out.Insert(shortName, "EXIF_FORMAT_SBYTE Unsupported");
284 case EXIF_FORMAT_BYTE:
286 out.Insert(shortName, "EXIF_FORMAT_BYTE Unsupported");
289 case EXIF_FORMAT_SRATIONAL:
291 auto values = reinterpret_cast<int*>(entry.data);
292 Dali::Property::Array array;
293 array.Add(values[0]);
294 array.Add(values[1]);
295 out.Insert(shortName, array);
298 case EXIF_FORMAT_UNDEFINED:
301 std::stringstream ss;
302 ss << "EXIF_FORMAT_UNDEFINED, size: " << entry.size << ", components: " << entry.components;
303 out.Insert(shortName, ss.str());
308 /// @brief Apply a transform to a buffer
309 bool Transform(const TransformFunctionArray& transformFunctions,
313 Pixel::Format pixelFormat)
315 auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
316 if(transformFunction)
318 transformFunction(buffer, width, height);
320 return bool(transformFunction);
323 /// @brief Auxiliar type to represent pixel data with different number of bytes
331 void Rotate180(PixelArray buffer, int width, int height)
333 // Destination pixel, set as the first pixel of screen
334 auto to = reinterpret_cast<PixelType<N>*>(buffer);
336 // Source pixel, as the image is flipped horizontally and vertically,
337 // the source pixel is the end of the buffer of size width * height
338 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
340 for(auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
342 std::swap(*from, *to);
347 void FlipHorizontal(PixelArray buffer, int width, int height)
349 for(auto iy = 0; iy < height; ++iy)
351 //Set the destination pixel as the beginning of the row
352 auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
353 //Set the source pixel as the end of the row to flip in X axis
354 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
355 for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
357 std::swap(*from, *to);
363 void FlipVertical(PixelArray buffer, int width, int height)
365 //Transform vertically only
366 for(auto iy = 0; iy < height / 2; ++iy)
368 for(auto ix = 0; ix < width; ++ix)
370 auto to = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
371 auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
372 std::swap(*from, *to);
378 void Transpose(PixelArray buffer, int width, int height)
380 using PixelT = PixelType<N>;
382 data.Resize(width * height);
383 auto dataPtr = data.Begin();
385 auto original = reinterpret_cast<PixelT*>(buffer);
386 std::copy(original, original + width * height, dataPtr);
389 for(auto iy = 0; iy < width; ++iy)
391 for(auto ix = 0; ix < height; ++ix, ++to)
393 auto from = dataPtr + ix * width + iy;
400 void Rotate90(PixelArray buffer, int width, int height)
402 using PixelT = PixelType<N>;
404 data.Resize(width * height);
405 auto dataPtr = data.Begin();
407 auto original = reinterpret_cast<PixelT*>(buffer);
408 std::copy(original, original + width * height, dataPtr);
410 std::swap(width, height);
411 auto hw = width * height;
414 auto to = original + width - 1;
417 for(auto ix = width; --ix >= 0;)
419 for(auto iy = height; --iy >= 0; ++from)
429 void Transverse(PixelArray buffer, int width, int height)
431 using PixelT = PixelType<N>;
433 data.Resize(width * height);
434 auto dataPtr = data.Begin();
436 auto original = reinterpret_cast<PixelT*>(buffer);
437 std::copy(original, original + width * height, dataPtr);
440 for(auto iy = 0; iy < width; iy++)
442 for(auto ix = 0; ix < height; ix++)
444 auto from = dataPtr + (height - ix) * width - 1 - iy;
452 void Rotate270(PixelArray buffer, int width, int height)
454 using PixelT = PixelType<N>;
456 data.Resize(width * height);
457 auto dataPtr = data.Begin();
459 auto original = reinterpret_cast<PixelT*>(buffer);
460 std::copy(original, original + width * height, dataPtr);
463 std::swap(width, height);
464 auto hw = width * height;
466 auto* to = original + hw - width;
467 auto* from = dataPtr;
471 for(auto ix = width; --ix >= 0;)
473 for(auto iy = height; --iy >= 0;)
487 namespace TizenPlatform
489 JpegTransform ConvertExifOrientation(ExifData* exifData);
490 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight);
492 bool LoadJpegHeader(FILE* fp, unsigned int& width, unsigned int& height)
494 // using libjpeg API to avoid having to read the whole file in a buffer
495 struct jpeg_decompress_struct cinfo;
496 struct JpegErrorState jerr;
497 cinfo.err = jpeg_std_error(&jerr.errorManager);
499 jerr.errorManager.output_message = JpegOutputMessageHandler;
500 jerr.errorManager.error_exit = JpegErrorHandler;
502 // On error exit from the JPEG lib, control will pass via JpegErrorHandler
503 // into this branch body for cleanup and error return:
504 if(setjmp(jerr.jumpBuffer))
506 DALI_LOG_ERROR("setjmp failed\n");
507 jpeg_destroy_decompress(&cinfo);
511 // jpeg_create_decompress internally uses C casts
512 #pragma GCC diagnostic push
513 #pragma GCC diagnostic ignored "-Wold-style-cast"
514 jpeg_create_decompress(&cinfo);
515 #pragma GCC diagnostic pop
517 jpeg_stdio_src(&cinfo, fp);
519 // Check header to see if it is JPEG file
520 if(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
522 DALI_LOG_ERROR("jpeg_read_header failed\n");
524 jpeg_destroy_decompress(&cinfo);
528 width = cinfo.image_width;
529 height = cinfo.image_height;
531 jpeg_destroy_decompress(&cinfo);
535 bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
538 FILE* const fp = input.file;
540 if(fseek(fp, 0, SEEK_END))
542 DALI_LOG_ERROR("Error seeking to end of file\n");
546 long positionIndicator = ftell(fp);
547 unsigned int jpegBufferSize = 0u;
548 if(positionIndicator > -1L)
550 jpegBufferSize = static_cast<unsigned int>(positionIndicator);
553 if(0u == jpegBufferSize)
555 DALI_LOG_ERROR("Jpeg buffer size error\n");
559 if(fseek(fp, 0, SEEK_SET))
561 DALI_LOG_ERROR("Error seeking to start of file\n");
565 Vector<unsigned char> jpegBuffer;
568 jpegBuffer.Resize(jpegBufferSize);
572 DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
575 unsigned char* const jpegBufferPtr = jpegBuffer.Begin();
577 // Pull the compressed JPEG image bytes out of a file and into memory:
578 if(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize)
580 DALI_LOG_ERROR("Error on image file read.\n");
584 if(fseek(fp, 0, SEEK_SET))
586 DALI_LOG_ERROR("Error seeking to start of file\n");
589 auto jpeg = MakeJpegDecompressor();
593 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
597 auto transform = JpegTransform::NONE;
600 auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
602 if(exifData && input.reorientationRequested)
604 transform = ConvertExifOrientation(exifData.get());
607 std::unique_ptr<Property::Map> exifMap;
608 exifMap.reset(new Property::Map());
610 for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
612 auto content = exifData->ifd[k];
613 for(auto i = 0u; i < content->count; ++i)
615 auto&& tag = content->entries[i];
616 const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
619 AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
624 // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
625 int chrominanceSubsampling = -1;
626 int preXformImageWidth = 0, preXformImageHeight = 0;
628 // In Ubuntu, the turbojpeg version is not correct. so build error occurs.
629 // Temporarily separate Ubuntu and other profiles.
630 #ifndef DALI_PROFILE_UBUNTU
631 int jpegColorspace = -1;
632 if(tjDecompressHeader3(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace) == -1)
634 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
635 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
638 if(tjDecompressHeader2(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling) == -1)
640 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
641 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
645 if(preXformImageWidth == 0 || preXformImageHeight == 0)
647 DALI_LOG_ERROR("Invalid Image!\n");
651 int requiredWidth = input.scalingParameters.dimensions.GetWidth();
652 int requiredHeight = input.scalingParameters.dimensions.GetHeight();
654 // If transform is a 90 or 270 degree rotation, the logical width and height
655 // request from the client needs to be adjusted to account by effectively
656 // rotating that too, and the final width and height need to be swapped:
657 int postXformImageWidth = preXformImageWidth;
658 int postXformImageHeight = preXformImageHeight;
660 int scaledPreXformWidth = preXformImageWidth;
661 int scaledPreXformHeight = preXformImageHeight;
662 int scaledPostXformWidth = postXformImageWidth;
663 int scaledPostXformHeight = postXformImageHeight;
665 TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
667 // Colorspace conversion options
668 TJPF pixelLibJpegType = TJPF_RGB;
669 Pixel::Format pixelFormat = Pixel::RGB888;
670 #ifndef DALI_PROFILE_UBUNTU
671 switch(jpegColorspace)
674 // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
675 // YCbCr images must be converted to RGB before they can actually be displayed.
678 pixelLibJpegType = TJPF_RGB;
679 pixelFormat = Pixel::RGB888;
684 pixelLibJpegType = TJPF_GRAY;
685 pixelFormat = Pixel::L8;
691 pixelLibJpegType = TJPF_CMYK;
692 pixelFormat = Pixel::RGBA8888;
697 pixelLibJpegType = TJPF_RGB;
698 pixelFormat = Pixel::RGB888;
703 // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
704 bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
707 GetImplementation(bitmap).SetMetadata(std::move(exifMap));
709 auto bitmapPixelBuffer = bitmap.GetBuffer();
711 if(tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags) == -1)
713 std::string errorString = tjGetErrorStr();
715 if(IsJpegErrorFatal(errorString))
717 DALI_LOG_ERROR("%s\n", errorString.c_str());
722 DALI_LOG_WARNING("%s\n", errorString.c_str());
726 const unsigned int bufferWidth = GetTextureDimension(scaledPreXformWidth);
727 const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight);
732 case JpegTransform::NONE:
737 // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
738 case JpegTransform::ROTATE_180:
740 static auto rotate180Functions = TransformFunctionArray{
745 result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
748 case JpegTransform::ROTATE_270:
750 static auto rotate270Functions = TransformFunctionArray{
755 result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
758 case JpegTransform::ROTATE_90:
760 static auto rotate90Functions = TransformFunctionArray{
765 result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
768 case JpegTransform::FLIP_VERTICAL:
770 static auto flipVerticalFunctions = TransformFunctionArray{
775 result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
778 // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
779 case JpegTransform::FLIP_HORIZONTAL:
781 static auto flipHorizontalFunctions = TransformFunctionArray{
786 result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
789 case JpegTransform::TRANSPOSE:
791 static auto transposeFunctions = TransformFunctionArray{
796 result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
799 case JpegTransform::TRANSVERSE:
801 static auto transverseFunctions = TransformFunctionArray{
806 result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
811 DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
819 bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
822 FILE* const fp = input.file;
824 if(fseek(fp, 0, SEEK_END))
826 DALI_LOG_ERROR("Error seeking to end of file\n");
830 long positionIndicator = ftell(fp);
831 unsigned int jpegBufferSize = 0u;
832 if(positionIndicator > -1L)
834 jpegBufferSize = static_cast<unsigned int>(positionIndicator);
837 if(0u == jpegBufferSize)
839 DALI_LOG_ERROR("Jpeg buffer size error\n");
843 if(fseek(fp, 0, SEEK_SET))
845 DALI_LOG_ERROR("Error seeking to start of file\n");
849 Vector<unsigned char> jpegBuffer;
852 jpegBuffer.Resize(jpegBufferSize);
856 DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
859 unsigned char* const jpegBufferPtr = jpegBuffer.Begin();
861 // Pull the compressed JPEG image bytes out of a file and into memory:
862 if(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize)
864 DALI_LOG_ERROR("Error on image file read.\n");
868 if(fseek(fp, 0, SEEK_SET))
870 DALI_LOG_ERROR("Error seeking to start of file\n");
873 auto jpeg = MakeJpegDecompressor();
877 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
881 auto transform = JpegTransform::NONE;
884 auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
886 if(exifData && input.reorientationRequested)
888 transform = ConvertExifOrientation(exifData.get());
891 std::unique_ptr<Property::Map> exifMap;
892 exifMap.reset(new Property::Map());
894 for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
896 auto content = exifData->ifd[k];
897 for(auto i = 0u; i < content->count; ++i)
899 auto&& tag = content->entries[i];
900 const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
903 AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
908 // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
909 int chrominanceSubsampling = -1;
910 int preXformImageWidth = 0, preXformImageHeight = 0;
912 int jpegColorspace = -1;
913 if(tjDecompressHeader3(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace) == -1)
915 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
916 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
919 if(preXformImageWidth == 0 || preXformImageHeight == 0)
921 DALI_LOG_ERROR("Invalid Image!\n");
925 int requiredWidth = input.scalingParameters.dimensions.GetWidth();
926 int requiredHeight = input.scalingParameters.dimensions.GetHeight();
928 // If transform is a 90 or 270 degree rotation, the logical width and height
929 // request from the client needs to be adjusted to account by effectively
930 // rotating that too, and the final width and height need to be swapped:
931 int postXformImageWidth = preXformImageWidth;
932 int postXformImageHeight = preXformImageHeight;
934 int scaledPreXformWidth = preXformImageWidth;
935 int scaledPreXformHeight = preXformImageHeight;
936 int scaledPostXformWidth = postXformImageWidth;
937 int scaledPostXformHeight = postXformImageHeight;
939 TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
941 auto decodeToYuvString = EnvironmentVariable::GetEnvironmentVariable(DECODE_JPEG_TO_YUV_ENV);
942 bool decodeToYuv = decodeToYuvString ? std::atoi(decodeToYuvString) : false;
943 int decodeResult = -1;
946 // Now we support YUV420 only
947 if(decodeToYuv && chrominanceSubsampling == TJSAMP_420 && transform == JpegTransform::NONE)
949 unsigned char* planes[3];
951 // Allocate buffers for each plane and decompress the jpeg buffer into the buffers
952 for(int i = 0; i < 3; i++)
954 auto planeSize = tjPlaneSizeYUV(i, scaledPostXformWidth, 0, scaledPostXformHeight, chrominanceSubsampling);
956 unsigned char* buffer = static_cast<unsigned char*>(malloc(planeSize));
959 DALI_LOG_ERROR("Buffer allocation is failed [%d]\n", planeSize);
960 pixelBuffers.clear();
964 int width, height, planeWidth;
969 width = scaledPostXformWidth;
970 height = scaledPostXformHeight;
971 planeWidth = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
975 width = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
976 height = tjPlaneHeight(i, scaledPostXformHeight, chrominanceSubsampling);
980 Internal::Adaptor::PixelBufferPtr internal = Internal::Adaptor::PixelBuffer::New(buffer, planeSize, width, height, planeWidth, Pixel::L8);
981 Dali::Devel::PixelBuffer bitmap = Devel::PixelBuffer(internal.Get());
985 pixelBuffers.push_back(bitmap);
988 decodeResult = tjDecompressToYUVPlanes(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char**>(&planes), scaledPostXformWidth, nullptr, scaledPostXformHeight, flags);
989 if(decodeResult == -1)
991 std::string errorString = tjGetErrorStr();
993 if(IsJpegErrorFatal(errorString))
995 DALI_LOG_ERROR("%s\n", errorString.c_str());
996 pixelBuffers.clear();
1001 DALI_LOG_WARNING("%s\n", errorString.c_str());
1009 // Colorspace conversion options
1010 TJPF pixelLibJpegType = TJPF_RGB;
1011 Pixel::Format pixelFormat = Pixel::RGB888;
1013 switch(jpegColorspace)
1016 // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
1017 // YCbCr images must be converted to RGB before they can actually be displayed.
1020 pixelLibJpegType = TJPF_RGB;
1021 pixelFormat = Pixel::RGB888;
1026 pixelLibJpegType = TJPF_GRAY;
1027 pixelFormat = Pixel::L8;
1033 pixelLibJpegType = TJPF_CMYK;
1034 pixelFormat = Pixel::RGBA8888;
1039 pixelLibJpegType = TJPF_RGB;
1040 pixelFormat = Pixel::RGB888;
1045 // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
1046 Dali::Devel::PixelBuffer bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
1049 GetImplementation(bitmap).SetMetadata(std::move(exifMap));
1051 auto bitmapPixelBuffer = bitmap.GetBuffer();
1053 decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
1054 if(decodeResult == -1)
1056 std::string errorString = tjGetErrorStr();
1058 if(IsJpegErrorFatal(errorString))
1060 DALI_LOG_ERROR("%s\n", errorString.c_str());
1065 DALI_LOG_WARNING("%s\n", errorString.c_str());
1068 pixelBuffers.push_back(bitmap);
1070 const unsigned int bufferWidth = GetTextureDimension(scaledPreXformWidth);
1071 const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight);
1075 case JpegTransform::NONE:
1080 // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
1081 case JpegTransform::ROTATE_180:
1083 static auto rotate180Functions = TransformFunctionArray{
1088 result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1091 case JpegTransform::ROTATE_270:
1093 static auto rotate270Functions = TransformFunctionArray{
1098 result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1101 case JpegTransform::ROTATE_90:
1103 static auto rotate90Functions = TransformFunctionArray{
1108 result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1111 case JpegTransform::FLIP_VERTICAL:
1113 static auto flipVerticalFunctions = TransformFunctionArray{
1118 result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1121 // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
1122 case JpegTransform::FLIP_HORIZONTAL:
1124 static auto flipHorizontalFunctions = TransformFunctionArray{
1129 result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1132 case JpegTransform::TRANSPOSE:
1134 static auto transposeFunctions = TransformFunctionArray{
1139 result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1142 case JpegTransform::TRANSVERSE:
1144 static auto transverseFunctions = TransformFunctionArray{
1149 result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1154 DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
1163 bool EncodeToJpeg(const unsigned char* const pixelBuffer, Vector<unsigned char>& encodedPixels, const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality)
1167 DALI_LOG_ERROR("Null input buffer\n");
1171 // Translate pixel format enum:
1172 int jpegPixelFormat = -1;
1178 jpegPixelFormat = TJPF_RGB;
1181 case Pixel::RGBA8888:
1183 // Ignore the alpha:
1184 jpegPixelFormat = TJPF_RGBX;
1187 case Pixel::BGRA8888:
1189 // Ignore the alpha:
1190 jpegPixelFormat = TJPF_BGRX;
1195 DALI_LOG_ERROR("Unsupported pixel format for encoding to JPEG.\n");
1200 // Assert quality is in the documented allowable range of the jpeg-turbo lib:
1201 DALI_ASSERT_DEBUG(quality >= 1);
1202 DALI_ASSERT_DEBUG(quality <= 100);
1212 // Initialise a JPEG codec:
1214 auto jpeg = MakeJpegCompressor();
1217 DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
1221 // Safely wrap the jpeg codec's buffer in case we are about to throw, then
1222 // save the pixels to a persistent buffer that we own and let our cleaner
1223 // class clean up the buffer as it goes out of scope:
1224 auto dstBuffer = MakeJpegMemory();
1226 // Run the compressor:
1227 unsigned long dstBufferSize = 0;
1228 const int flags = 0;
1230 if(tjCompress2(jpeg.get(),
1231 const_cast<unsigned char*>(pixelBuffer),
1236 SetPointer(dstBuffer),
1242 DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
1246 encodedPixels.Resize(dstBufferSize);
1247 memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
1252 JpegTransform ConvertExifOrientation(ExifData* exifData)
1254 auto transform = JpegTransform::NONE;
1255 ExifEntry* const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
1256 int orientation = 0;
1259 orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
1264 transform = JpegTransform::NONE;
1269 transform = JpegTransform::FLIP_HORIZONTAL;
1274 transform = JpegTransform::ROTATE_180;
1279 transform = JpegTransform::FLIP_VERTICAL;
1284 transform = JpegTransform::TRANSPOSE;
1289 transform = JpegTransform::ROTATE_90;
1294 transform = JpegTransform::TRANSVERSE;
1299 transform = JpegTransform::ROTATE_270;
1304 // Try to keep loading the file, but let app developer know there was something fishy:
1305 DALI_LOG_WARNING("Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry);
1313 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight)
1315 bool success = true;
1316 if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1318 std::swap(requiredWidth, requiredHeight);
1319 std::swap(postXformImageWidth, postXformImageHeight);
1322 // Apply the special rules for when there are one or two zeros in requested dimensions:
1323 const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions(ImageDimensions(postXformImageWidth, postXformImageHeight), ImageDimensions(requiredWidth, requiredHeight));
1324 requiredWidth = correctedDesired.GetWidth();
1325 requiredHeight = correctedDesired.GetHeight();
1327 // Rescale image during decode using one of the decoder's built-in rescaling
1328 // ratios (expected to be powers of 2), keeping the final image at least as
1329 // wide and high as was requested:
1332 tjscalingfactor* factors = tjGetScalingFactors(&numFactors);
1335 DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
1340 // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
1341 // apply it if the application requested one of those:
1342 // (use a switch case here so this code will fail to compile if other modes are added)
1343 bool downscale = true;
1344 switch(samplingMode)
1346 case SamplingMode::BOX:
1347 case SamplingMode::BOX_THEN_NEAREST:
1348 case SamplingMode::BOX_THEN_LINEAR:
1349 case SamplingMode::DONT_CARE:
1354 case SamplingMode::NO_FILTER:
1355 case SamplingMode::NEAREST:
1356 case SamplingMode::LINEAR:
1363 int scaleFactorIndex(0);
1366 // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
1367 for(int i = 1; i < numFactors; ++i)
1369 bool widthLessRequired = TJSCALED(postXformImageWidth, factors[i]) < requiredWidth;
1370 bool heightLessRequired = TJSCALED(postXformImageHeight, factors[i]) < requiredHeight;
1371 // If either scaled dimension is smaller than the desired one, we were done at the last iteration
1372 if((fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired))
1376 // If both dimensions are smaller than the desired one, we were done at the last iteration:
1377 if((fittingMode == FittingMode::SHRINK_TO_FIT) && (widthLessRequired && heightLessRequired))
1381 // If the width is smaller than the desired one, we were done at the last iteration:
1382 if(fittingMode == FittingMode::FIT_WIDTH && widthLessRequired)
1386 // If the width is smaller than the desired one, we were done at the last iteration:
1387 if(fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired)
1391 // This factor stays is within our fitting mode constraint so remember it:
1392 scaleFactorIndex = i;
1396 // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
1397 for(int i = scaleFactorIndex; i < numFactors; ++i)
1399 // Continue downscaling to below maximum texture size (if possible)
1400 scaleFactorIndex = i;
1402 if(TJSCALED(postXformImageWidth, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()) &&
1403 TJSCALED(postXformImageHeight, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()))
1405 // Current scale-factor downscales to below maximum texture size
1410 // We have finally chosen the scale-factor, return width/height values
1411 if(scaleFactorIndex > 0)
1413 preXformImageWidth = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
1414 preXformImageHeight = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
1415 postXformImageWidth = TJSCALED(postXformImageWidth, (factors[scaleFactorIndex]));
1416 postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
1423 ExifHandle LoadExifData(FILE* fp)
1425 auto exifData = MakeNullExifData();
1426 unsigned char dataBuffer[1024];
1428 if(fseek(fp, 0, SEEK_SET))
1430 DALI_LOG_ERROR("Error seeking to start of file\n");
1434 auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
1435 exif_loader_new(), exif_loader_unref};
1439 int size = fread(dataBuffer, 1, sizeof(dataBuffer), fp);
1444 if(!exif_loader_write(exifLoader.get(), dataBuffer, size))
1450 exifData.reset(exif_loader_get_data(exifLoader.get()));
1456 bool LoadJpegHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
1458 unsigned int requiredWidth = input.scalingParameters.dimensions.GetWidth();
1459 unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1460 FILE* const fp = input.file;
1462 bool success = false;
1464 unsigned int headerWidth;
1465 unsigned int headerHeight;
1467 success = LoadJpegHeader(fp, headerWidth, headerHeight);
1470 auto transform = JpegTransform::NONE;
1472 if(input.reorientationRequested)
1474 auto exifData = LoadExifData(fp);
1477 transform = ConvertExifOrientation(exifData.get());
1481 if(requiredWidth == 0 && requiredHeight == 0)
1483 if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1485 std::swap(headerWidth, headerHeight);
1490 int preXformImageWidth = headerWidth;
1491 int preXformImageHeight = headerHeight;
1492 int postXformImageWidth = headerWidth;
1493 int postXformImageHeight = headerHeight;
1495 success = TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
1498 headerWidth = postXformImageWidth;
1499 headerHeight = postXformImageHeight;
1502 width = headerWidth;
1503 height = headerHeight;
1509 } // namespace TizenPlatform