2 * Copyright (c) 2023 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>
26 #include <libexif/exif-data.h>
27 #include <libexif/exif-loader.h>
28 #include <libexif/exif-tag.h>
29 #include <turbojpeg.h>
34 #include <dali/public-api/object/property-map.h>
35 #include <dali/public-api/object/property-array.h>
36 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
40 #include <dali/internal/legacy/tizen/platform-capabilities.h>
41 #include <dali/internal/imaging/common/image-operations.h>
42 #include <dali/devel-api/adaptor-framework/image-loading.h>
43 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
48 namespace Pixel = Dali::Pixel;
49 using PixelArray = unsigned char*;
50 const unsigned int DECODED_L8 = 1;
51 const unsigned int DECODED_RGB888 = 3;
52 const unsigned int DECODED_RGBA8888 = 4;
54 /** Transformations that can be applied to decoded pixels to respect exif orientation
55 * codes in image headers */
56 enum class JpegTransform
58 NONE, //< no transformation 0th-Row = top & 0th-Column = left
59 FLIP_HORIZONTAL, //< horizontal flip 0th-Row = top & 0th-Column = right
60 FLIP_VERTICAL, //< vertical flip 0th-Row = bottom & 0th-Column = right
61 TRANSPOSE, //< transpose across UL-to-LR axis 0th-Row = bottom & 0th-Column = left
62 TRANSVERSE, //< transpose across UR-to-LL axis 0th-Row = left & 0th-Column = top
63 ROTATE_90, //< 90-degree clockwise rotation 0th-Row = right & 0th-Column = top
64 ROTATE_180, //< 180-degree rotation 0th-Row = right & 0th-Column = bottom
65 ROTATE_270, //< 270-degree clockwise (or 90 ccw) 0th-Row = left & 0th-Column = bottom
69 * @brief Error handling bookeeping for the JPEG Turbo library's
70 * setjmp/longjmp simulated exceptions.
74 struct jpeg_error_mgr errorManager;
79 * @brief Called by the JPEG library when it hits an error.
80 * We jump out of the library so our loader code can return an error.
82 void JpegErrorHandler ( j_common_ptr cinfo )
84 DALI_LOG_ERROR( "JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n" );
85 /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
86 JpegErrorState * myerr = reinterpret_cast<JpegErrorState *>( cinfo->err );
88 /* Return control to the setjmp point */
89 longjmp( myerr->jumpBuffer, 1 );
92 void JpegOutputMessageHandler( j_common_ptr cinfo )
94 /* Stop libjpeg from printing to stderr - Do Nothing */
98 * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
99 * the JPEG to be displayed and fatal errors.
101 bool IsJpegErrorFatal( const std::string& errorMessage )
103 if( ( errorMessage.find("Corrupt JPEG data") != std::string::npos ) ||
104 ( errorMessage.find("Invalid SOS parameters") != std::string::npos ) ||
105 ( errorMessage.find("Invalid JPEG file structure") != std::string::npos ) ||
106 ( errorMessage.find("Unsupported JPEG process") != std::string::npos ) ||
107 ( errorMessage.find("Unsupported marker type") != std::string::npos ) ||
108 ( errorMessage.find("Bogus marker length") != std::string::npos ) ||
109 ( errorMessage.find("Bogus DQT index") != std::string::npos ) ||
110 ( errorMessage.find("Bogus Huffman table definition") != std::string::npos ))
117 // helpers for safe exif memory handling
118 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
120 ExifHandle MakeNullExifData()
122 return ExifHandle{nullptr, exif_data_free};
125 ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size)
127 return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
130 // Helpers for safe Jpeg memory handling
131 using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
133 JpegHandle MakeJpegCompressor()
135 return JpegHandle{tjInitCompress(), tjDestroy};
138 JpegHandle MakeJpegDecompressor()
140 return JpegHandle{tjInitDecompress(), tjDestroy};
143 using JpegMemoryHandle = std::unique_ptr<unsigned char, decltype(tjFree)*>;
145 JpegMemoryHandle MakeJpegMemory()
147 return JpegMemoryHandle{nullptr, tjFree};
150 template<class T, class Deleter>
151 class UniquePointerSetter final
154 UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
155 : mUniquePointer(uniquePointer),
159 /// @brief Pointer to Pointer cast operator
160 operator T** () { return &mRawPointer; }
162 /// @brief Destructor, reset the unique_ptr
163 ~UniquePointerSetter() { mUniquePointer.reset(mRawPointer); }
166 std::unique_ptr<T, Deleter>& mUniquePointer;
170 template<typename T, typename Deleter>
171 UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
173 return UniquePointerSetter<T, Deleter>{uniquePointer};
176 using TransformFunction = std::function<void(PixelArray,unsigned, unsigned)>;
177 using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
179 /// @brief Select the transform function depending on the pixel format
180 TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
181 Pixel::Format pixelFormat)
183 auto function = TransformFunction{};
185 int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
186 switch( decodedPixelSize )
190 function = functions[0];
195 function = functions[1];
198 case DECODED_RGBA8888:
200 function = functions[2];
205 DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
206 function = functions[1];
213 // Storing Exif fields as properties
214 template<class R, class V>
215 R ConvertExifNumeric( const ExifEntry& entry )
217 return static_cast<R>((*reinterpret_cast<V*>(entry.data)));
220 void AddExifFieldPropertyMap( Dali::Property::Map& out, const ExifEntry& entry, ExifIfd ifd )
222 auto shortName = std::string(exif_tag_get_name_in_ifd(entry.tag, ifd ));
223 switch( entry.format )
225 case EXIF_FORMAT_ASCII:
227 out.Insert( shortName, std::string( reinterpret_cast<char *>(entry.data), entry.size ) );
230 case EXIF_FORMAT_SHORT:
232 out.Insert( shortName, ConvertExifNumeric<int, uint16_t>(entry) );
235 case EXIF_FORMAT_LONG:
237 out.Insert( shortName, ConvertExifNumeric<int, uint32_t>(entry) );
240 case EXIF_FORMAT_SSHORT:
242 out.Insert( shortName, ConvertExifNumeric<int, int16_t>(entry) );
245 case EXIF_FORMAT_SLONG:
247 out.Insert( shortName, ConvertExifNumeric<int, int32_t>(entry) );
250 case EXIF_FORMAT_FLOAT:
252 out.Insert (shortName, ConvertExifNumeric<float, float>(entry) );
255 case EXIF_FORMAT_DOUBLE:
257 out.Insert( shortName, ConvertExifNumeric<float, double>(entry) );
260 case EXIF_FORMAT_RATIONAL:
262 auto values = reinterpret_cast<unsigned int*>( entry.data );
263 Dali::Property::Array array;
264 array.Add( static_cast<int>(values[0]) );
265 array.Add( static_cast<int>(values[1]) );
266 out.Insert(shortName, array);
269 case EXIF_FORMAT_SBYTE:
271 out.Insert(shortName, "EXIF_FORMAT_SBYTE Unsupported");
274 case EXIF_FORMAT_BYTE:
276 out.Insert(shortName, "EXIF_FORMAT_BYTE Unsupported");
279 case EXIF_FORMAT_SRATIONAL:
281 auto values = reinterpret_cast<int*>( entry.data );
282 Dali::Property::Array array;
283 array.Add(values[0]);
284 array.Add(values[1]);
285 out.Insert(shortName, array);
288 case EXIF_FORMAT_UNDEFINED:
291 std::stringstream ss;
292 ss << "EXIF_FORMAT_UNDEFINED, size: " << entry.size << ", components: " << entry.components;
293 out.Insert( shortName, ss.str());
298 /// @brief Apply a transform to a buffer
299 bool Transform(const TransformFunctionArray& transformFunctions,
303 Pixel::Format pixelFormat )
305 auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
306 if(transformFunction)
308 transformFunction(buffer, width, height);
310 return bool(transformFunction);
313 /// @brief Auxiliar type to represent pixel data with different number of bytes
321 void FlipVertical(PixelArray buffer, int width, int height)
323 // Destination pixel, set as the first pixel of screen
324 auto to = reinterpret_cast<PixelType<N>*>( buffer );
326 // Source pixel, as the image is flipped horizontally and vertically,
327 // the source pixel is the end of the buffer of size width * height
328 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
330 for (auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
332 std::swap(*from, *to);
337 void FlipHorizontal(PixelArray buffer, int width, int height)
339 for(auto iy = 0; iy < height; ++iy)
341 //Set the destination pixel as the beginning of the row
342 auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
343 //Set the source pixel as the end of the row to flip in X axis
344 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
345 for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
347 std::swap(*from, *to);
353 void Transpose(PixelArray buffer, int width, int height)
355 //Transform vertically only
356 for(auto iy = 0; iy < height / 2; ++iy)
358 for(auto ix = 0; ix < width; ++ix)
360 auto to = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
361 auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
362 std::swap(*from, *to);
368 void Transverse(PixelArray buffer, int width, int height)
370 using PixelT = PixelType<N>;
372 data.ResizeUninitialized( width * height );
373 auto dataPtr = data.Begin();
375 auto original = reinterpret_cast<PixelT*>(buffer);
376 std::copy(original, original + width * height, dataPtr);
379 for( auto iy = 0; iy < width; ++iy )
381 for( auto ix = 0; ix < height; ++ix, ++to )
383 auto from = dataPtr + ix * width + iy;
391 void Rotate90(PixelArray buffer, int width, int height)
393 using PixelT = PixelType<N>;
395 data.ResizeUninitialized(width * height);
396 auto dataPtr = data.Begin();
398 auto original = reinterpret_cast<PixelT*>(buffer);
399 std::copy(original, original + width * height, dataPtr);
401 std::swap(width, height);
402 auto hw = width * height;
405 auto to = original + width - 1;
408 for(auto ix = width; --ix >= 0;)
410 for(auto iy = height; --iy >= 0; ++from)
420 void Rotate180(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++ )
435 auto from = dataPtr + (height - ix) * width - 1 - iy;
444 void Rotate270(PixelArray buffer, int width, int height)
446 using PixelT = PixelType<N>;
448 data.ResizeUninitialized(width * height);
449 auto dataPtr = data.Begin();
451 auto original = reinterpret_cast<PixelT*>(buffer);
452 std::copy(original, original + width * height, dataPtr);
455 std::swap(width, height);
456 auto hw = width * height;
458 auto* to = original + hw - width;
459 auto* from = dataPtr;
463 for(auto ix = width; --ix >= 0;)
465 for(auto iy = height; --iy >= 0;)
476 * @brief Helper function to convert from Turbo Jpeg Pixel Format as TJPF_CMYK to RGB888 by naive method.
478 * @param[in] cmykBuffer buffer of cmyk.
479 * @param[in] rgbBuffer buffer of Pixel::RGB888
480 * @param[in] width width of image.
481 * @param[in] height height of image.
483 void ConvertTjpfCMYKToRGB888(PixelArray __restrict__ cmykBuffer, PixelArray __restrict__ rgbBuffer, int32_t width, int32_t height)
485 const int32_t pixelCount = width * height;
486 const uint8_t cmykBpp = 4u;
487 const uint8_t bpp = 3u;
489 const PixelArray cmykBufferEnd = cmykBuffer + pixelCount * cmykBpp;
490 // Convert every pixel
491 while(cmykBuffer != cmykBufferEnd)
493 const uint16_t channelK = static_cast<uint16_t>(*(cmykBuffer + 3u));
494 *(rgbBuffer + 0u) = static_cast<uint8_t>(static_cast<uint16_t>(*(cmykBuffer + 0u)) * channelK / 255);
495 *(rgbBuffer + 1u) = static_cast<uint8_t>(static_cast<uint16_t>(*(cmykBuffer + 1u)) * channelK / 255);
496 *(rgbBuffer + 2u) = static_cast<uint8_t>(static_cast<uint16_t>(*(cmykBuffer + 2u)) * channelK / 255);
497 cmykBuffer += cmykBpp;
506 namespace TizenPlatform
509 JpegTransform ConvertExifOrientation(ExifData* exifData);
510 bool TransformSize( int requiredWidth, int requiredHeight,
511 FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
512 JpegTransform transform,
513 int& preXformImageWidth, int& preXformImageHeight,
514 int& postXformImageWidth, int& postXformImageHeight );
516 bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height )
518 // using libjpeg API to avoid having to read the whole file in a buffer
519 struct jpeg_decompress_struct cinfo;
520 struct JpegErrorState jerr;
521 cinfo.err = jpeg_std_error( &jerr.errorManager );
523 jerr.errorManager.output_message = JpegOutputMessageHandler;
524 jerr.errorManager.error_exit = JpegErrorHandler;
526 // On error exit from the JPEG lib, control will pass via JpegErrorHandler
527 // into this branch body for cleanup and error return:
528 if(setjmp(jerr.jumpBuffer))
530 jpeg_destroy_decompress(&cinfo);
534 // jpeg_create_decompress internally uses C casts
535 #pragma GCC diagnostic push
536 #pragma GCC diagnostic ignored "-Wold-style-cast"
537 jpeg_create_decompress( &cinfo );
538 #pragma GCC diagnostic pop
540 jpeg_stdio_src( &cinfo, fp );
542 // Check header to see if it is JPEG file
543 if( jpeg_read_header( &cinfo, TRUE ) != JPEG_HEADER_OK )
546 jpeg_destroy_decompress( &cinfo );
550 width = cinfo.image_width;
551 height = cinfo.image_height;
553 jpeg_destroy_decompress( &cinfo );
557 bool LoadBitmapFromJpeg( const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap )
560 FILE* const fp = input.file;
562 if( fseek(fp,0,SEEK_END) )
564 DALI_LOG_ERROR("Error seeking to end of file\n");
568 long positionIndicator = ftell(fp);
569 unsigned int jpegBufferSize = 0u;
570 if( positionIndicator > -1L )
572 jpegBufferSize = static_cast<unsigned int>(positionIndicator);
575 if( 0u == jpegBufferSize )
580 if( fseek(fp, 0, SEEK_SET) )
582 DALI_LOG_ERROR("Error seeking to start of file\n");
586 Vector<unsigned char> jpegBuffer;
589 jpegBuffer.ResizeUninitialized( jpegBufferSize );
593 DALI_LOG_ERROR( "Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U );
596 unsigned char * const jpegBufferPtr = jpegBuffer.Begin();
598 // Pull the compressed JPEG image bytes out of a file and into memory:
599 if( fread( jpegBufferPtr, 1, jpegBufferSize, fp ) != jpegBufferSize )
601 DALI_LOG_WARNING("Error on image file read.\n");
605 if( fseek(fp, 0, SEEK_SET) )
607 DALI_LOG_ERROR("Error seeking to start of file\n");
610 auto jpeg = MakeJpegDecompressor();
614 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
618 auto transform = JpegTransform::NONE;
621 auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
623 if( exifData && input.reorientationRequested )
625 transform = ConvertExifOrientation(exifData.get());
628 std::unique_ptr<Property::Map> exifMap;
629 exifMap.reset( new Property::Map() );
631 for( auto k = 0u; k < EXIF_IFD_COUNT; ++k )
633 auto content = exifData->ifd[k];
634 for (auto i = 0u; i < content->count; ++i)
636 auto &&tag = content->entries[i];
637 const char *shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
640 AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
645 // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
646 int chrominanceSubsampling = -1;
647 int preXformImageWidth = 0, preXformImageHeight = 0;
649 int jpegColorspace = -1;
650 if( tjDecompressHeader3( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace ) == -1 )
652 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
653 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
656 if(preXformImageWidth == 0 || preXformImageHeight == 0)
658 DALI_LOG_WARNING("Invalid Image!\n");
662 int requiredWidth = input.scalingParameters.dimensions.GetWidth();
663 int requiredHeight = input.scalingParameters.dimensions.GetHeight();
665 // If transform is a 90 or 270 degree rotation, the logical width and height
666 // request from the client needs to be adjusted to account by effectively
667 // rotating that too, and the final width and height need to be swapped:
668 int postXformImageWidth = preXformImageWidth;
669 int postXformImageHeight = preXformImageHeight;
672 int scaledPreXformWidth = preXformImageWidth;
673 int scaledPreXformHeight = preXformImageHeight;
674 int scaledPostXformWidth = postXformImageWidth;
675 int scaledPostXformHeight = postXformImageHeight;
677 TransformSize( requiredWidth, requiredHeight,
678 input.scalingParameters.scalingMode,
679 input.scalingParameters.samplingMode,
681 scaledPreXformWidth, scaledPreXformHeight,
682 scaledPostXformWidth, scaledPostXformHeight );
685 // Colorspace conversion options
686 TJPF pixelLibJpegType = TJPF_RGB;
687 Pixel::Format pixelFormat = Pixel::RGB888;
688 switch (jpegColorspace)
691 // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
692 // YCbCr images must be converted to RGB before they can actually be displayed.
695 pixelLibJpegType = TJPF_RGB;
696 pixelFormat = Pixel::RGB888;
701 pixelLibJpegType = TJPF_GRAY;
702 pixelFormat = Pixel::L8;
708 pixelLibJpegType = TJPF_CMYK;
709 pixelFormat = Pixel::RGB888;
714 pixelLibJpegType = TJPF_RGB;
715 pixelFormat = Pixel::RGB888;
719 // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
720 bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
723 GetImplementation(bitmap).SetMetadata( std::move(exifMap) );
725 auto bitmapPixelBuffer = bitmap.GetBuffer();
727 if(pixelLibJpegType == TJPF_CMYK)
729 // Currently we support only for 4 bytes per each CMYK pixel.
730 const uint8_t cmykBytesPerPixel = 4u;
732 uint8_t* cmykBuffer = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * scaledPostXformWidth * scaledPostXformHeight * cmykBytesPerPixel));
734 int decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(cmykBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
735 if(DALI_UNLIKELY(decodeResult == -1))
737 std::string errorString = tjGetErrorStr();
739 if(IsJpegErrorFatal(errorString))
741 DALI_LOG_ERROR("%s\n", errorString.c_str());
747 DALI_LOG_WARNING("%s\n", errorString.c_str());
750 ConvertTjpfCMYKToRGB888(cmykBuffer, bitmapPixelBuffer, scaledPostXformWidth, scaledPostXformHeight);
756 int decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
757 if(DALI_UNLIKELY(decodeResult == -1))
759 std::string errorString = tjGetErrorStr();
761 if(IsJpegErrorFatal(errorString))
763 DALI_LOG_ERROR("%s\n", errorString.c_str());
768 DALI_LOG_WARNING("%s\n", errorString.c_str());
773 const unsigned int bufferWidth = GetTextureDimension( scaledPreXformWidth );
774 const unsigned int bufferHeight = GetTextureDimension( scaledPreXformHeight );
779 case JpegTransform::NONE:
784 // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
785 case JpegTransform::ROTATE_180:
787 static auto rotate180Functions = TransformFunctionArray {
792 result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
795 case JpegTransform::ROTATE_270:
797 static auto rotate270Functions = TransformFunctionArray {
802 result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
805 case JpegTransform::ROTATE_90:
807 static auto rotate90Functions = TransformFunctionArray {
812 result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
815 case JpegTransform::FLIP_VERTICAL:
817 static auto flipVerticalFunctions = TransformFunctionArray {
822 result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
825 // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
826 case JpegTransform::FLIP_HORIZONTAL:
828 static auto flipHorizontalFunctions = TransformFunctionArray {
833 result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
836 case JpegTransform::TRANSPOSE:
838 static auto transposeFunctions = TransformFunctionArray {
843 result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
846 case JpegTransform::TRANSVERSE:
848 static auto transverseFunctions = TransformFunctionArray {
853 result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
858 DALI_LOG_ERROR( "Unsupported JPEG Orientation transformation: %x.\n", transform );
866 bool EncodeToJpeg( const unsigned char* const pixelBuffer, Vector< unsigned char >& encodedPixels,
867 const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality )
872 DALI_LOG_ERROR("Null input buffer\n");
876 // Translate pixel format enum:
877 int jpegPixelFormat = -1;
879 switch( pixelFormat )
883 jpegPixelFormat = TJPF_RGB;
886 case Pixel::RGBA8888:
889 jpegPixelFormat = TJPF_RGBX;
892 case Pixel::BGRA8888:
895 jpegPixelFormat = TJPF_BGRX;
900 DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG.\n" );
905 // Assert quality is in the documented allowable range of the jpeg-turbo lib:
906 DALI_ASSERT_DEBUG( quality >= 1 );
907 DALI_ASSERT_DEBUG( quality <= 100 );
917 // Initialise a JPEG codec:
919 auto jpeg = MakeJpegCompressor();
922 DALI_LOG_ERROR( "JPEG Compressor init failed: %s\n", tjGetErrorStr() );
927 // Safely wrap the jpeg codec's buffer in case we are about to throw, then
928 // save the pixels to a persistent buffer that we own and let our cleaner
929 // class clean up the buffer as it goes out of scope:
930 auto dstBuffer = MakeJpegMemory();
932 // Run the compressor:
933 unsigned long dstBufferSize = 0;
936 if( tjCompress2( jpeg.get(),
937 const_cast<unsigned char*>(pixelBuffer),
939 jpegPixelFormat, SetPointer(dstBuffer), &dstBufferSize,
940 TJSAMP_444, quality, flags ) )
942 DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
946 encodedPixels.ResizeUninitialized( dstBufferSize );
947 memcpy( encodedPixels.Begin(), dstBuffer.get(), dstBufferSize );
953 JpegTransform ConvertExifOrientation(ExifData* exifData)
955 auto transform = JpegTransform::NONE;
956 ExifEntry * const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
960 orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
961 switch( orientation )
965 transform = JpegTransform::NONE;
970 transform = JpegTransform::FLIP_HORIZONTAL;
975 transform = JpegTransform::FLIP_VERTICAL;
980 transform = JpegTransform::TRANSPOSE;
985 transform = JpegTransform::TRANSVERSE;
990 transform = JpegTransform::ROTATE_90;
995 transform = JpegTransform::ROTATE_180;
1000 transform = JpegTransform::ROTATE_270;
1005 // Try to keep loading the file, but let app developer know there was something fishy:
1006 DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry );
1014 bool TransformSize( int requiredWidth, int requiredHeight,
1015 FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
1016 JpegTransform transform,
1017 int& preXformImageWidth, int& preXformImageHeight,
1018 int& postXformImageWidth, int& postXformImageHeight )
1020 bool success = true;
1022 if( transform == JpegTransform::ROTATE_90 || transform == JpegTransform::ROTATE_270 || transform == JpegTransform::ROTATE_180 || transform == JpegTransform::TRANSVERSE)
1024 std::swap( requiredWidth, requiredHeight );
1025 std::swap( postXformImageWidth, postXformImageHeight );
1028 // Apply the special rules for when there are one or two zeros in requested dimensions:
1029 const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions( ImageDimensions( postXformImageWidth, postXformImageHeight), ImageDimensions( requiredWidth, requiredHeight ) );
1030 requiredWidth = correctedDesired.GetWidth();
1031 requiredHeight = correctedDesired.GetHeight();
1033 // Rescale image during decode using one of the decoder's built-in rescaling
1034 // ratios (expected to be powers of 2), keeping the final image at least as
1035 // wide and high as was requested:
1038 tjscalingfactor* factors = tjGetScalingFactors( &numFactors );
1039 if( factors == NULL )
1041 DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
1046 // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
1047 // apply it if the application requested one of those:
1048 // (use a switch case here so this code will fail to compile if other modes are added)
1049 bool downscale = true;
1050 switch( samplingMode )
1052 case SamplingMode::BOX:
1053 case SamplingMode::BOX_THEN_NEAREST:
1054 case SamplingMode::BOX_THEN_LINEAR:
1055 case SamplingMode::DONT_CARE:
1060 case SamplingMode::NO_FILTER:
1061 case SamplingMode::NEAREST:
1062 case SamplingMode::LINEAR:
1069 int scaleFactorIndex( 0 );
1072 // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
1073 for( int i = 1; i < numFactors; ++i )
1075 bool widthLessRequired = TJSCALED( postXformImageWidth, factors[i]) < requiredWidth;
1076 bool heightLessRequired = TJSCALED( postXformImageHeight, factors[i]) < requiredHeight;
1077 // If either scaled dimension is smaller than the desired one, we were done at the last iteration
1078 if ( (fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired) )
1082 // If both dimensions are smaller than the desired one, we were done at the last iteration:
1083 if ( (fittingMode == FittingMode::SHRINK_TO_FIT) && ( widthLessRequired && heightLessRequired ) )
1087 // If the width is smaller than the desired one, we were done at the last iteration:
1088 if ( fittingMode == FittingMode::FIT_WIDTH && widthLessRequired )
1092 // If the width is smaller than the desired one, we were done at the last iteration:
1093 if ( fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired )
1097 // This factor stays is within our fitting mode constraint so remember it:
1098 scaleFactorIndex = i;
1102 // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
1103 for( int i = scaleFactorIndex; i < numFactors; ++i )
1105 // Continue downscaling to below maximum texture size (if possible)
1106 scaleFactorIndex = i;
1108 if( TJSCALED(postXformImageWidth, (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) &&
1109 TJSCALED(postXformImageHeight, (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) )
1111 // Current scale-factor downscales to below maximum texture size
1116 // We have finally chosen the scale-factor, return width/height values
1117 if( scaleFactorIndex > 0 )
1119 preXformImageWidth = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
1120 preXformImageHeight = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
1121 postXformImageWidth = TJSCALED(postXformImageWidth, (factors[scaleFactorIndex]));
1122 postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
1129 ExifHandle LoadExifData( FILE* fp )
1131 auto exifData = MakeNullExifData();
1132 unsigned char dataBuffer[1024];
1134 if( fseek( fp, 0, SEEK_SET ) )
1136 DALI_LOG_ERROR("Error seeking to start of file\n");
1140 auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
1141 exif_loader_new(), exif_loader_unref };
1145 int size = fread( dataBuffer, 1, sizeof( dataBuffer ), fp );
1150 if( ! exif_loader_write( exifLoader.get(), dataBuffer, size ) )
1156 exifData.reset( exif_loader_get_data( exifLoader.get() ) );
1162 bool LoadJpegHeader( const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height )
1164 unsigned int requiredWidth = input.scalingParameters.dimensions.GetWidth();
1165 unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1166 FILE* const fp = input.file;
1168 bool success = false;
1170 // If rotatedSizeRequested is true, just get size
1171 if( input.rotatedSizeRequested )
1173 unsigned int headerWidth;
1174 unsigned int headerHeight;
1175 if( LoadJpegHeader( fp, headerWidth, headerHeight ) )
1177 auto transform = JpegTransform::NONE;
1178 if( input.reorientationRequested )
1180 auto exifData = LoadExifData( fp );
1183 transform = ConvertExifOrientation(exifData.get());
1185 if( transform == JpegTransform::ROTATE_90 || transform == JpegTransform::ROTATE_270 || transform == JpegTransform::TRANSPOSE || transform == JpegTransform::TRANSVERSE)
1187 std::swap( headerWidth, headerHeight );
1191 width = headerWidth;
1192 height = headerHeight;
1197 if( requiredWidth == 0 && requiredHeight == 0 )
1199 success = LoadJpegHeader( fp, width, height );
1203 // Double check we get the same width/height from the header
1204 unsigned int headerWidth;
1205 unsigned int headerHeight;
1206 if( LoadJpegHeader( fp, headerWidth, headerHeight ) )
1208 auto transform = JpegTransform::NONE;
1210 if( input.reorientationRequested )
1212 auto exifData = LoadExifData( fp );
1215 transform = ConvertExifOrientation(exifData.get());
1218 int preXformImageWidth = headerWidth;
1219 int preXformImageHeight = headerHeight;
1220 int postXformImageWidth = headerWidth;
1221 int postXformImageHeight = headerHeight;
1223 success = TransformSize( requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight );
1226 width = postXformImageWidth;
1227 height = postXformImageHeight;
1233 width = headerWidth;
1234 height = headerHeight;
1242 } // namespace TizenPlatform