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>
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/image-loading.h>
40 #include <dali/internal/imaging/common/image-operations.h>
41 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
42 #include <dali/internal/legacy/tizen/platform-capabilities.h>
47 namespace Pixel = Dali::Pixel;
48 using PixelArray = unsigned char*;
49 const unsigned int DECODED_L8 = 1;
50 const unsigned int DECODED_RGB888 = 3;
51 const unsigned int DECODED_RGBA8888 = 4;
53 /** Transformations that can be applied to decoded pixels to respect exif orientation
54 * codes in image headers */
55 enum class JpegTransform
57 NONE, //< no transformation 0th-Row = top & 0th-Column = left
58 FLIP_HORIZONTAL, //< horizontal flip 0th-Row = top & 0th-Column = right
59 ROTATE_180, //< 180-degree rotation 0th-Row = bottom & 0th-Column = right
60 FLIP_VERTICAL, //< vertical flip 0th-Row = bottom & 0th-Column = left
61 TRANSPOSE, //< transpose across UL-to-LR axis 0th-Row = left & 0th-Column = top
62 ROTATE_90, //< 90-degree clockwise rotation 0th-Row = right & 0th-Column = top
63 TRANSVERSE, //< transpose across UR-to-LL axis 0th-Row = right & 0th-Column = bottom
64 ROTATE_270, //< 270-degree clockwise (or 90 ccw) 0th-Row = left & 0th-Column = bottom
68 * @brief Error handling bookeeping for the JPEG Turbo library's
69 * setjmp/longjmp simulated exceptions.
73 struct jpeg_error_mgr errorManager;
78 * @brief Called by the JPEG library when it hits an error.
79 * We jump out of the library so our loader code can return an error.
81 void JpegErrorHandler(j_common_ptr cinfo)
83 DALI_LOG_ERROR("JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n");
84 /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
85 JpegErrorState* myerr = reinterpret_cast<JpegErrorState*>(cinfo->err);
87 /* Return control to the setjmp point */
88 longjmp(myerr->jumpBuffer, 1);
91 void JpegOutputMessageHandler(j_common_ptr cinfo)
93 /* Stop libjpeg from printing to stderr - Do Nothing */
97 * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
98 * the JPEG to be displayed and fatal errors.
100 bool IsJpegErrorFatal(const std::string& errorMessage)
102 if((errorMessage.find("Corrupt JPEG data") != std::string::npos) ||
103 (errorMessage.find("Invalid SOS parameters") != std::string::npos) ||
104 (errorMessage.find("Invalid JPEG file structure") != std::string::npos) ||
105 (errorMessage.find("Unsupported JPEG process") != std::string::npos) ||
106 (errorMessage.find("Unsupported marker type") != std::string::npos) ||
107 (errorMessage.find("Bogus marker length") != std::string::npos) ||
108 (errorMessage.find("Bogus DQT index") != std::string::npos) ||
109 (errorMessage.find("Bogus Huffman table definition") != std::string::npos))
116 // helpers for safe exif memory handling
117 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
119 ExifHandle MakeNullExifData()
121 return ExifHandle{nullptr, exif_data_free};
124 ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size)
126 return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
129 // Helpers for safe Jpeg memory handling
130 using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
132 JpegHandle MakeJpegCompressor()
134 return JpegHandle{tjInitCompress(), tjDestroy};
137 JpegHandle MakeJpegDecompressor()
139 return JpegHandle{tjInitDecompress(), tjDestroy};
142 using JpegMemoryHandle = std::unique_ptr<unsigned char, decltype(tjFree)*>;
144 JpegMemoryHandle MakeJpegMemory()
146 return JpegMemoryHandle{nullptr, tjFree};
149 template<class T, class Deleter>
150 class UniquePointerSetter final
153 UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
154 : mUniquePointer(uniquePointer),
159 /// @brief Pointer to Pointer cast operator
165 /// @brief Destructor, reset the unique_ptr
166 ~UniquePointerSetter()
168 mUniquePointer.reset(mRawPointer);
172 std::unique_ptr<T, Deleter>& mUniquePointer;
176 template<typename T, typename Deleter>
177 UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
179 return UniquePointerSetter<T, Deleter>{uniquePointer};
182 using TransformFunction = std::function<void(PixelArray, unsigned, unsigned)>;
183 using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
185 /// @brief Select the transform function depending on the pixel format
186 TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
187 Pixel::Format pixelFormat)
189 auto function = TransformFunction{};
191 int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
192 switch(decodedPixelSize)
196 function = functions[0];
201 function = functions[1];
204 case DECODED_RGBA8888:
206 function = functions[2];
211 DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
212 function = functions[1];
219 // Storing Exif fields as properties
220 template<class R, class V>
221 R ConvertExifNumeric(const ExifEntry& entry)
223 return static_cast<R>((*reinterpret_cast<V*>(entry.data)));
226 void AddExifFieldPropertyMap(Dali::Property::Map& out, const ExifEntry& entry, ExifIfd ifd)
228 auto shortName = std::string(exif_tag_get_name_in_ifd(entry.tag, ifd));
231 case EXIF_FORMAT_ASCII:
233 out.Insert(shortName, std::string(reinterpret_cast<char*>(entry.data), entry.size));
236 case EXIF_FORMAT_SHORT:
238 out.Insert(shortName, ConvertExifNumeric<int, uint16_t>(entry));
241 case EXIF_FORMAT_LONG:
243 out.Insert(shortName, ConvertExifNumeric<int, uint32_t>(entry));
246 case EXIF_FORMAT_SSHORT:
248 out.Insert(shortName, ConvertExifNumeric<int, int16_t>(entry));
251 case EXIF_FORMAT_SLONG:
253 out.Insert(shortName, ConvertExifNumeric<int, int32_t>(entry));
256 case EXIF_FORMAT_FLOAT:
258 out.Insert(shortName, ConvertExifNumeric<float, float>(entry));
261 case EXIF_FORMAT_DOUBLE:
263 out.Insert(shortName, ConvertExifNumeric<float, double>(entry));
266 case EXIF_FORMAT_RATIONAL:
268 auto values = reinterpret_cast<unsigned int*>(entry.data);
269 Dali::Property::Array array;
270 array.Add(static_cast<int>(values[0]));
271 array.Add(static_cast<int>(values[1]));
272 out.Insert(shortName, array);
275 case EXIF_FORMAT_SBYTE:
277 out.Insert(shortName, "EXIF_FORMAT_SBYTE Unsupported");
280 case EXIF_FORMAT_BYTE:
282 out.Insert(shortName, "EXIF_FORMAT_BYTE Unsupported");
285 case EXIF_FORMAT_SRATIONAL:
287 auto values = reinterpret_cast<int*>(entry.data);
288 Dali::Property::Array array;
289 array.Add(values[0]);
290 array.Add(values[1]);
291 out.Insert(shortName, array);
294 case EXIF_FORMAT_UNDEFINED:
297 std::stringstream ss;
298 ss << "EXIF_FORMAT_UNDEFINED, size: " << entry.size << ", components: " << entry.components;
299 out.Insert(shortName, ss.str());
304 /// @brief Apply a transform to a buffer
305 bool Transform(const TransformFunctionArray& transformFunctions,
309 Pixel::Format pixelFormat)
311 auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
312 if(transformFunction)
314 transformFunction(buffer, width, height);
316 return bool(transformFunction);
319 /// @brief Auxiliar type to represent pixel data with different number of bytes
327 void Rotate180(PixelArray buffer, int width, int height)
329 // Destination pixel, set as the first pixel of screen
330 auto to = reinterpret_cast<PixelType<N>*>(buffer);
332 // Source pixel, as the image is flipped horizontally and vertically,
333 // the source pixel is the end of the buffer of size width * height
334 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
336 for(auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
338 std::swap(*from, *to);
343 void FlipHorizontal(PixelArray buffer, int width, int height)
345 for(auto iy = 0; iy < height; ++iy)
347 //Set the destination pixel as the beginning of the row
348 auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
349 //Set the source pixel as the end of the row to flip in X axis
350 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
351 for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
353 std::swap(*from, *to);
359 void FlipVertical(PixelArray buffer, int width, int height)
361 //Transform vertically only
362 for(auto iy = 0; iy < height / 2; ++iy)
364 for(auto ix = 0; ix < width; ++ix)
366 auto to = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
367 auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
368 std::swap(*from, *to);
374 void Transpose(PixelArray buffer, int width, int height)
376 using PixelT = PixelType<N>;
378 data.ResizeUninitialized(width * height);
379 auto dataPtr = data.Begin();
381 auto original = reinterpret_cast<PixelT*>(buffer);
382 std::copy(original, original + width * height, dataPtr);
385 for(auto iy = 0; iy < width; ++iy)
387 for(auto ix = 0; ix < height; ++ix, ++to)
389 auto from = dataPtr + ix * width + iy;
396 void Rotate90(PixelArray buffer, int width, int height)
398 using PixelT = PixelType<N>;
400 data.ResizeUninitialized(width * height);
401 auto dataPtr = data.Begin();
403 auto original = reinterpret_cast<PixelT*>(buffer);
404 std::copy(original, original + width * height, dataPtr);
406 std::swap(width, height);
407 auto hw = width * height;
410 auto to = original + width - 1;
413 for(auto ix = width; --ix >= 0;)
415 for(auto iy = height; --iy >= 0; ++from)
425 void Transverse(PixelArray buffer, int width, int height)
427 using PixelT = PixelType<N>;
429 data.ResizeUninitialized(width * height);
430 auto dataPtr = data.Begin();
432 auto original = reinterpret_cast<PixelT*>(buffer);
433 std::copy(original, original + width * height, dataPtr);
436 for(auto iy = 0; iy < width; iy++)
438 for(auto ix = 0; ix < height; ix++)
440 auto from = dataPtr + (height - ix) * width - 1 - iy;
448 void Rotate270(PixelArray buffer, int width, int height)
450 using PixelT = PixelType<N>;
452 data.ResizeUninitialized(width * height);
453 auto dataPtr = data.Begin();
455 auto original = reinterpret_cast<PixelT*>(buffer);
456 std::copy(original, original + width * height, dataPtr);
459 std::swap(width, height);
460 auto hw = width * height;
462 auto* to = original + hw - width;
463 auto* from = dataPtr;
467 for(auto ix = width; --ix >= 0;)
469 for(auto iy = height; --iy >= 0;)
483 namespace TizenPlatform
485 JpegTransform ConvertExifOrientation(ExifData* exifData);
486 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight);
488 bool LoadJpegHeader(FILE* fp, unsigned int& width, unsigned int& height)
490 // using libjpeg API to avoid having to read the whole file in a buffer
491 struct jpeg_decompress_struct cinfo;
492 struct JpegErrorState jerr;
493 cinfo.err = jpeg_std_error(&jerr.errorManager);
495 jerr.errorManager.output_message = JpegOutputMessageHandler;
496 jerr.errorManager.error_exit = JpegErrorHandler;
498 // On error exit from the JPEG lib, control will pass via JpegErrorHandler
499 // into this branch body for cleanup and error return:
500 if(setjmp(jerr.jumpBuffer))
502 DALI_LOG_ERROR("setjmp failed\n");
503 jpeg_destroy_decompress(&cinfo);
507 // jpeg_create_decompress internally uses C casts
508 #pragma GCC diagnostic push
509 #pragma GCC diagnostic ignored "-Wold-style-cast"
510 jpeg_create_decompress(&cinfo);
511 #pragma GCC diagnostic pop
513 jpeg_stdio_src(&cinfo, fp);
515 // Check header to see if it is JPEG file
516 if(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
518 DALI_LOG_ERROR("jpeg_read_header failed\n");
520 jpeg_destroy_decompress(&cinfo);
524 width = cinfo.image_width;
525 height = cinfo.image_height;
527 jpeg_destroy_decompress(&cinfo);
531 bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
534 FILE* const fp = input.file;
536 if(fseek(fp, 0, SEEK_END))
538 DALI_LOG_ERROR("Error seeking to end of file\n");
542 long positionIndicator = ftell(fp);
543 unsigned int jpegBufferSize = 0u;
544 if(positionIndicator > -1L)
546 jpegBufferSize = static_cast<unsigned int>(positionIndicator);
549 if(0u == jpegBufferSize)
551 DALI_LOG_ERROR("Jpeg buffer size error\n");
555 if(fseek(fp, 0, SEEK_SET))
557 DALI_LOG_ERROR("Error seeking to start of file\n");
561 Vector<unsigned char> jpegBuffer;
564 jpegBuffer.ResizeUninitialized(jpegBufferSize);
568 DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
571 unsigned char* const jpegBufferPtr = jpegBuffer.Begin();
573 // Pull the compressed JPEG image bytes out of a file and into memory:
574 if(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize)
576 DALI_LOG_ERROR("Error on image file read.\n");
580 if(fseek(fp, 0, SEEK_SET))
582 DALI_LOG_ERROR("Error seeking to start of file\n");
585 auto jpeg = MakeJpegDecompressor();
589 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
593 auto transform = JpegTransform::NONE;
596 auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
598 if(exifData && input.reorientationRequested)
600 transform = ConvertExifOrientation(exifData.get());
603 std::unique_ptr<Property::Map> exifMap;
604 exifMap.reset(new Property::Map());
606 if(DALI_LIKELY(exifData))
608 for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
610 auto content = exifData->ifd[k];
611 for(auto i = 0u; i < content->count; ++i)
613 auto&& tag = content->entries[i];
614 const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
617 AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
623 // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
624 int chrominanceSubsampling = -1;
625 int preXformImageWidth = 0, preXformImageHeight = 0;
627 // In Ubuntu, the turbojpeg version is not correct. so build error occurs.
628 // Temporarily separate Ubuntu and other profiles.
629 #ifndef DALI_PROFILE_UBUNTU
630 int jpegColorspace = -1;
631 if(tjDecompressHeader3(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace) == -1)
633 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
634 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
637 if(tjDecompressHeader2(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling) == -1)
639 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
640 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
644 if(preXformImageWidth == 0 || preXformImageHeight == 0)
646 DALI_LOG_ERROR("Invalid Image!\n");
650 int requiredWidth = input.scalingParameters.dimensions.GetWidth();
651 int requiredHeight = input.scalingParameters.dimensions.GetHeight();
653 // If transform is a 90 or 270 degree rotation, the logical width and height
654 // request from the client needs to be adjusted to account by effectively
655 // rotating that too, and the final width and height need to be swapped:
656 int postXformImageWidth = preXformImageWidth;
657 int postXformImageHeight = preXformImageHeight;
659 int scaledPreXformWidth = preXformImageWidth;
660 int scaledPreXformHeight = preXformImageHeight;
661 int scaledPostXformWidth = postXformImageWidth;
662 int scaledPostXformHeight = postXformImageHeight;
664 TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
666 // Colorspace conversion options
667 TJPF pixelLibJpegType = TJPF_RGB;
668 Pixel::Format pixelFormat = Pixel::RGB888;
669 #ifndef DALI_PROFILE_UBUNTU
670 switch(jpegColorspace)
673 // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
674 // YCbCr images must be converted to RGB before they can actually be displayed.
677 pixelLibJpegType = TJPF_RGB;
678 pixelFormat = Pixel::RGB888;
683 pixelLibJpegType = TJPF_GRAY;
684 pixelFormat = Pixel::L8;
690 pixelLibJpegType = TJPF_CMYK;
691 pixelFormat = Pixel::RGBA8888;
696 pixelLibJpegType = TJPF_RGB;
697 pixelFormat = Pixel::RGB888;
702 // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
703 bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
706 GetImplementation(bitmap).SetMetadata(std::move(exifMap));
708 auto bitmapPixelBuffer = bitmap.GetBuffer();
710 if(tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags) == -1)
712 std::string errorString = tjGetErrorStr();
714 if(IsJpegErrorFatal(errorString))
716 DALI_LOG_ERROR("%s\n", errorString.c_str());
721 DALI_LOG_WARNING("%s\n", errorString.c_str());
725 const unsigned int bufferWidth = GetTextureDimension(scaledPreXformWidth);
726 const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight);
731 case JpegTransform::NONE:
736 // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
737 case JpegTransform::ROTATE_180:
739 static auto rotate180Functions = TransformFunctionArray{
744 result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
747 case JpegTransform::ROTATE_270:
749 static auto rotate270Functions = TransformFunctionArray{
754 result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
757 case JpegTransform::ROTATE_90:
759 static auto rotate90Functions = TransformFunctionArray{
764 result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
767 case JpegTransform::FLIP_VERTICAL:
769 static auto flipVerticalFunctions = TransformFunctionArray{
774 result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
777 // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
778 case JpegTransform::FLIP_HORIZONTAL:
780 static auto flipHorizontalFunctions = TransformFunctionArray{
785 result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
788 case JpegTransform::TRANSPOSE:
790 static auto transposeFunctions = TransformFunctionArray{
795 result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
798 case JpegTransform::TRANSVERSE:
800 static auto transverseFunctions = TransformFunctionArray{
805 result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
810 DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
818 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)
822 DALI_LOG_ERROR("Null input buffer\n");
826 // Translate pixel format enum:
827 int jpegPixelFormat = -1;
833 jpegPixelFormat = TJPF_RGB;
836 case Pixel::RGBA8888:
839 jpegPixelFormat = TJPF_RGBX;
842 case Pixel::BGRA8888:
845 jpegPixelFormat = TJPF_BGRX;
850 DALI_LOG_ERROR("Unsupported pixel format for encoding to JPEG.\n");
855 // Assert quality is in the documented allowable range of the jpeg-turbo lib:
856 DALI_ASSERT_DEBUG(quality >= 1);
857 DALI_ASSERT_DEBUG(quality <= 100);
867 // Initialise a JPEG codec:
869 auto jpeg = MakeJpegCompressor();
872 DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
876 // Safely wrap the jpeg codec's buffer in case we are about to throw, then
877 // save the pixels to a persistent buffer that we own and let our cleaner
878 // class clean up the buffer as it goes out of scope:
879 auto dstBuffer = MakeJpegMemory();
881 // Run the compressor:
882 unsigned long dstBufferSize = 0;
885 if(tjCompress2(jpeg.get(),
886 const_cast<unsigned char*>(pixelBuffer),
891 SetPointer(dstBuffer),
897 DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
901 encodedPixels.ResizeUninitialized(dstBufferSize);
902 memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
907 JpegTransform ConvertExifOrientation(ExifData* exifData)
909 auto transform = JpegTransform::NONE;
910 ExifEntry* const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
914 orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
919 transform = JpegTransform::NONE;
924 transform = JpegTransform::FLIP_HORIZONTAL;
929 transform = JpegTransform::ROTATE_180;
934 transform = JpegTransform::FLIP_VERTICAL;
939 transform = JpegTransform::TRANSPOSE;
944 transform = JpegTransform::ROTATE_90;
949 transform = JpegTransform::TRANSVERSE;
954 transform = JpegTransform::ROTATE_270;
959 // Try to keep loading the file, but let app developer know there was something fishy:
960 DALI_LOG_WARNING("Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry);
968 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight)
971 if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
973 std::swap(requiredWidth, requiredHeight);
974 std::swap(postXformImageWidth, postXformImageHeight);
977 // Apply the special rules for when there are one or two zeros in requested dimensions:
978 const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions(ImageDimensions(postXformImageWidth, postXformImageHeight), ImageDimensions(requiredWidth, requiredHeight));
979 requiredWidth = correctedDesired.GetWidth();
980 requiredHeight = correctedDesired.GetHeight();
982 // Rescale image during decode using one of the decoder's built-in rescaling
983 // ratios (expected to be powers of 2), keeping the final image at least as
984 // wide and high as was requested:
987 tjscalingfactor* factors = tjGetScalingFactors(&numFactors);
990 DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
995 // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
996 // apply it if the application requested one of those:
997 // (use a switch case here so this code will fail to compile if other modes are added)
998 bool downscale = true;
1001 case SamplingMode::BOX:
1002 case SamplingMode::BOX_THEN_NEAREST:
1003 case SamplingMode::BOX_THEN_LINEAR:
1004 case SamplingMode::DONT_CARE:
1009 case SamplingMode::NO_FILTER:
1010 case SamplingMode::NEAREST:
1011 case SamplingMode::LINEAR:
1018 int scaleFactorIndex(0);
1021 // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
1022 for(int i = 1; i < numFactors; ++i)
1024 bool widthLessRequired = TJSCALED(postXformImageWidth, factors[i]) < requiredWidth;
1025 bool heightLessRequired = TJSCALED(postXformImageHeight, factors[i]) < requiredHeight;
1026 // If either scaled dimension is smaller than the desired one, we were done at the last iteration
1027 if((fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired))
1031 // If both dimensions are smaller than the desired one, we were done at the last iteration:
1032 if((fittingMode == FittingMode::SHRINK_TO_FIT) && (widthLessRequired && heightLessRequired))
1036 // If the width is smaller than the desired one, we were done at the last iteration:
1037 if(fittingMode == FittingMode::FIT_WIDTH && widthLessRequired)
1041 // If the width is smaller than the desired one, we were done at the last iteration:
1042 if(fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired)
1046 // This factor stays is within our fitting mode constraint so remember it:
1047 scaleFactorIndex = i;
1051 // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
1052 for(int i = scaleFactorIndex; i < numFactors; ++i)
1054 // Continue downscaling to below maximum texture size (if possible)
1055 scaleFactorIndex = i;
1057 if(TJSCALED(postXformImageWidth, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()) &&
1058 TJSCALED(postXformImageHeight, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()))
1060 // Current scale-factor downscales to below maximum texture size
1065 // We have finally chosen the scale-factor, return width/height values
1066 if(scaleFactorIndex > 0)
1068 preXformImageWidth = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
1069 preXformImageHeight = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
1070 postXformImageWidth = TJSCALED(postXformImageWidth, (factors[scaleFactorIndex]));
1071 postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
1078 ExifHandle LoadExifData(FILE* fp)
1080 auto exifData = MakeNullExifData();
1081 unsigned char dataBuffer[1024];
1083 if(fseek(fp, 0, SEEK_SET))
1085 DALI_LOG_ERROR("Error seeking to start of file\n");
1089 auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
1090 exif_loader_new(), exif_loader_unref};
1094 int size = fread(dataBuffer, 1, sizeof(dataBuffer), fp);
1099 if(!exif_loader_write(exifLoader.get(), dataBuffer, size))
1105 exifData.reset(exif_loader_get_data(exifLoader.get()));
1111 bool LoadJpegHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
1113 unsigned int requiredWidth = input.scalingParameters.dimensions.GetWidth();
1114 unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1115 FILE* const fp = input.file;
1117 bool success = false;
1119 unsigned int headerWidth;
1120 unsigned int headerHeight;
1122 success = LoadJpegHeader(fp, headerWidth, headerHeight);
1125 auto transform = JpegTransform::NONE;
1127 if(input.reorientationRequested)
1129 auto exifData = LoadExifData(fp);
1132 transform = ConvertExifOrientation(exifData.get());
1136 if(requiredWidth == 0 && requiredHeight == 0)
1138 if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1140 std::swap(headerWidth, headerHeight);
1145 int preXformImageWidth = headerWidth;
1146 int preXformImageHeight = headerHeight;
1147 int postXformImageWidth = headerWidth;
1148 int postXformImageHeight = headerHeight;
1150 success = TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
1153 headerWidth = postXformImageWidth;
1154 headerHeight = postXformImageHeight;
1157 width = headerWidth;
1158 height = headerHeight;
1164 } // namespace TizenPlatform