X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;ds=sidebyside;f=platform-abstractions%2Ftizen%2Fimage-loaders%2Floader-jpeg-turbo.cpp;h=3141c91e57118cfb910034fa1394cb8047484b00;hb=f4b05fe51ddba1d7d2035103ef3cfcd1d4741d3c;hp=a18e45a42f4df3ba801a0be6999ccbd9646e2cdb;hpb=5c0a37bfda9d6655dd4f17442dedcbb022b500fd;p=platform%2Fcore%2Fuifw%2Fdali-adaptor.git diff --git a/platform-abstractions/tizen/image-loaders/loader-jpeg-turbo.cpp b/platform-abstractions/tizen/image-loaders/loader-jpeg-turbo.cpp index a18e45a..3141c91 100755 --- a/platform-abstractions/tizen/image-loaders/loader-jpeg-turbo.cpp +++ b/platform-abstractions/tizen/image-loaders/loader-jpeg-turbo.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,14 @@ * */ -// INTERNAL HEADERS + // CLASS HEADER #include "loader-jpeg.h" -#include "resource-loading-client.h" -#include -#include -#include "platform-capabilities.h" // EXTERNAL HEADERS +#include +#include +#include +#include #include #include #include @@ -31,144 +31,459 @@ #include #include +#include +#include +#include -namespace Dali -{ -using Integration::Bitmap; -namespace TizenPlatform -{ +// INTERNAL HEADERS +#include "platform-capabilities.h" +#include "image-operations.h" +#include +#include namespace { - const unsigned DECODED_PIXEL_SIZE = 3; - const TJPF DECODED_PIXEL_LIBJPEG_TYPE = TJPF_RGB; +using Dali::Vector; +namespace Pixel = Dali::Pixel; +using PixelArray = unsigned char*; +const unsigned int DECODED_L8 = 1; +const unsigned int DECODED_RGB888 = 3; +const unsigned int DECODED_RGBA8888 = 4; + +/** Transformations that can be applied to decoded pixels to respect exif orientation + * codes in image headers */ +enum class JpegTransform +{ + NONE, //< no transformation 0th-Row = top & 0th-Column = left + FLIP_HORIZONTAL, //< horizontal flip 0th-Row = top & 0th-Column = right + FLIP_VERTICAL, //< vertical flip 0th-Row = bottom & 0th-Column = right + TRANSPOSE, //< transpose across UL-to-LR axis 0th-Row = bottom & 0th-Column = left + TRANSVERSE, //< transpose across UR-to-LL axis 0th-Row = left & 0th-Column = top + ROTATE_90, //< 90-degree clockwise rotation 0th-Row = right & 0th-Column = top + ROTATE_180, //< 180-degree rotation 0th-Row = right & 0th-Column = bottom + ROTATE_270, //< 270-degree clockwise (or 90 ccw) 0th-Row = left & 0th-Column = bottom +}; + +/** + * @brief Error handling bookeeping for the JPEG Turbo library's + * setjmp/longjmp simulated exceptions. + */ +struct JpegErrorState +{ + struct jpeg_error_mgr errorManager; + jmp_buf jumpBuffer; +}; + +/** + * @brief Called by the JPEG library when it hits an error. + * We jump out of the library so our loader code can return an error. + */ +void JpegErrorHandler ( j_common_ptr cinfo ) +{ + DALI_LOG_ERROR( "JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n" ); + /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */ + JpegErrorState * myerr = reinterpret_cast( cinfo->err ); - /** Transformations that can be applied to decoded pixels to respect exif orientation - * codes in image headers */ - enum JPGFORM_CODE - { - JPGFORM_NONE = 1, /* no transformation 0th-Row = top & 0th-Column = left */ - JPGFORM_FLIP_H, /* horizontal flip 0th-Row = top & 0th-Column = right */ - JPGFORM_FLIP_V, /* vertical flip 0th-Row = bottom & 0th-Column = right*/ - JPGFORM_TRANSPOSE, /* transpose across UL-to-LR axis 0th-Row = bottom & 0th-Column = left*/ - JPGFORM_TRANSVERSE,/* transpose across UR-to-LL axis 0th-Row = left & 0th-Column = top*/ - JPGFORM_ROT_90, /* 90-degree clockwise rotation 0th-Row = right & 0th-Column = top*/ - JPGFORM_ROT_180, /* 180-degree rotation 0th-Row = right & 0th-Column = bottom*/ - JPGFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) 0th-Row = left & 0th-Column = bottom*/ - }; + /* Return control to the setjmp point */ + longjmp( myerr->jumpBuffer, 1 ); +} - struct RGB888Type +void JpegOutputMessageHandler( j_common_ptr cinfo ) +{ + /* Stop libjpeg from printing to stderr - Do Nothing */ +} + +/** + * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow + * the JPEG to be displayed and fatal errors. + */ +bool IsJpegErrorFatal( const std::string& errorMessage ) +{ + if( ( errorMessage.find("Corrupt JPEG data") != std::string::npos ) || + ( errorMessage.find("Invalid SOS parameters") != std::string::npos ) || + ( errorMessage.find("Invalid JPEG file structure") != std::string::npos ) || + ( errorMessage.find("Unsupported JPEG process") != std::string::npos ) || + ( errorMessage.find("Unsupported marker type") != std::string::npos ) || + ( errorMessage.find("Bogus marker length") != std::string::npos ) || + ( errorMessage.find("Bogus DQT index") != std::string::npos ) || + ( errorMessage.find("Bogus Huffman table definition") != std::string::npos )) { - char R; - char G; - char B; - }; + return false; + } + return true; +} + +// helpers for safe exif memory handling +using ExifHandle = std::unique_ptr; + +ExifHandle MakeNullExifData() +{ + return ExifHandle{nullptr, exif_data_free}; +} + +ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size) +{ + return ExifHandle{exif_data_new_from_data(data, size), exif_data_free}; +} + +// Helpers for safe Jpeg memory handling +using JpegHandle = std::unique_ptr; + +JpegHandle MakeJpegCompressor() +{ + return JpegHandle{tjInitCompress(), tjDestroy}; +} + +JpegHandle MakeJpegDecompressor() +{ + return JpegHandle{tjInitDecompress(), tjDestroy}; +} - /** - * @brief Error handling bookeeping for the JPEG Turbo library's - * setjmp/longjmp simulated exceptions. - */ - struct JpegErrorState { - struct jpeg_error_mgr errorManager; - jmp_buf jumpBuffer; - }; +using JpegMemoryHandle = std::unique_ptr; - /** - * @brief Called by the JPEG library when it hits an error. - * We jump out of the library so our loader code can return an error. - */ - void JpegErrorHandler ( j_common_ptr cinfo ) +JpegMemoryHandle MakeJpegMemory() +{ + return JpegMemoryHandle{nullptr, tjFree}; +} + +template +class UniquePointerSetter final +{ +public: + UniquePointerSetter(std::unique_ptr& uniquePointer) + : mUniquePointer(uniquePointer), + mRawPointer(nullptr) + {} + + /// @brief Pointer to Pointer cast operator + operator T** () { return &mRawPointer; } + + /// @brief Destructor, reset the unique_ptr + ~UniquePointerSetter() { mUniquePointer.reset(mRawPointer); } + +private: + std::unique_ptr& mUniquePointer; + T* mRawPointer; +}; + +template +UniquePointerSetter SetPointer(std::unique_ptr& uniquePointer) +{ + return UniquePointerSetter{uniquePointer}; +} + +using TransformFunction = std::function; +using TransformFunctionArray = std::array; // 1, 3 and 4 bytes per pixel + +/// @brief Select the transform function depending on the pixel format +TransformFunction GetTransformFunction(const TransformFunctionArray& functions, + Pixel::Format pixelFormat) +{ + auto function = TransformFunction{}; + + int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat); + switch( decodedPixelSize ) { - DALI_LOG_ERROR( "JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n" ); - /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */ - JpegErrorState * myerr = reinterpret_cast( cinfo->err ); + case DECODED_L8: + { + function = functions[0]; + break; + } + case DECODED_RGB888: + { + function = functions[1]; + break; + } + case DECODED_RGBA8888: + { + function = functions[2]; + break; + } + default: + { + DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!"); + function = functions[1]; + break; + } + } + return function; +} + +// Storing Exif fields as properties +template +R ConvertExifNumeric( const ExifEntry& entry ) +{ + return static_cast((*reinterpret_cast(entry.data))); +} - /* Return control to the setjmp point */ - longjmp( myerr->jumpBuffer, 1 ); +void AddExifFieldPropertyMap( Dali::Property::Map& out, const ExifEntry& entry, ExifIfd ifd ) +{ + auto shortName = std::string(exif_tag_get_name_in_ifd(entry.tag, ifd )); + switch( entry.format ) + { + case EXIF_FORMAT_ASCII: + { + out.Insert( shortName, std::string(reinterpret_cast(entry.data)) ); + break; + } + case EXIF_FORMAT_SHORT: + { + out.Insert( shortName, ConvertExifNumeric(entry) ); + break; + } + case EXIF_FORMAT_LONG: + { + out.Insert( shortName, ConvertExifNumeric(entry) ); + break; + } + case EXIF_FORMAT_SSHORT: + { + out.Insert( shortName, ConvertExifNumeric(entry) ); + break; + } + case EXIF_FORMAT_SLONG: + { + out.Insert( shortName, ConvertExifNumeric(entry) ); + break; + } + case EXIF_FORMAT_FLOAT: + { + out.Insert (shortName, ConvertExifNumeric(entry) ); + break; + } + case EXIF_FORMAT_DOUBLE: + { + out.Insert( shortName, ConvertExifNumeric(entry) ); + break; + } + case EXIF_FORMAT_RATIONAL: + { + auto values = reinterpret_cast( entry.data ); + Dali::Property::Array array; + array.Add( static_cast(values[0]) ); + array.Add( static_cast(values[1]) ); + out.Insert(shortName, array); + break; + } + case EXIF_FORMAT_SBYTE: + { + out.Insert(shortName, "EXIF_FORMAT_SBYTE Unsupported"); + break; + } + case EXIF_FORMAT_BYTE: + { + out.Insert(shortName, "EXIF_FORMAT_BYTE Unsupported"); + break; + } + case EXIF_FORMAT_SRATIONAL: + { + auto values = reinterpret_cast( entry.data ); + Dali::Property::Array array; + array.Add(values[0]); + array.Add(values[1]); + out.Insert(shortName, array); + break; + } + case EXIF_FORMAT_UNDEFINED: + default: + { + std::stringstream ss; + ss << "EXIF_FORMAT_UNDEFINED, size: " << entry.size << ", components: " << entry.components; + out.Insert( shortName, ss.str()); + } } +} - void JpegOutputMessageHandler( j_common_ptr cinfo ) +/// @brief Apply a transform to a buffer +bool Transform(const TransformFunctionArray& transformFunctions, + PixelArray buffer, + int width, + int height, + Pixel::Format pixelFormat ) +{ + auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat); + if(transformFunction) { - /* Stop libjpeg from printing to stderr - Do Nothing */ + transformFunction(buffer, width, height); } + return bool(transformFunction); +} + +/// @brief Auxiliar type to represent pixel data with different number of bytes +template +struct PixelType +{ + char _[N]; +}; + +template +void FlipVertical(PixelArray buffer, int width, int height) +{ + // Destination pixel, set as the first pixel of screen + auto to = reinterpret_cast*>( buffer ); + + // Source pixel, as the image is flipped horizontally and vertically, + // the source pixel is the end of the buffer of size width * height + auto from = reinterpret_cast*>(buffer) + width * height - 1; - /** Simple struct to ensure xif data is deleted. */ - struct ExifAutoPtr + for (auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from) { - ExifAutoPtr( ExifData* data) - :mData( data ) - {} + std::swap(*from, *to); + } +} - ~ExifAutoPtr() +template +void FlipHorizontal(PixelArray buffer, int width, int height) +{ + for(auto iy = 0; iy < height; ++iy) + { + //Set the destination pixel as the beginning of the row + auto to = reinterpret_cast*>(buffer) + width * iy; + //Set the source pixel as the end of the row to flip in X axis + auto from = reinterpret_cast*>(buffer) + width * (iy + 1) - 1; + for(auto ix = 0; ix < width / 2; ++ix, ++to, --from) { - exif_data_free( mData); + std::swap(*from, *to); } - ExifData *mData; - }; + } +} - /** simple class to enforce clean-up of JPEG structures. */ - struct AutoJpg +template +void Transpose(PixelArray buffer, int width, int height) +{ + //Transform vertically only + for(auto iy = 0; iy < height / 2; ++iy) { - AutoJpg(const tjhandle jpgHandle) - : mHnd(jpgHandle) + for(auto ix = 0; ix < width; ++ix) { + auto to = reinterpret_cast*>(buffer) + iy * width + ix; + auto from = reinterpret_cast*>(buffer) + (height - 1 - iy) * width + ix; + std::swap(*from, *to); } + } +} - ~AutoJpg() - { - // clean up JPG resources - tjDestroy( mHnd ); - } +template +void Transverse(PixelArray buffer, int width, int height) +{ + using PixelT = PixelType; + Vector data; + data.Resize( width * height ); + auto dataPtr = data.Begin(); + + auto original = reinterpret_cast(buffer); + std::copy(original, original + width * height, dataPtr); - tjhandle GetHandle() const + auto to = original; + for( auto iy = 0; iy < width; ++iy ) + { + for( auto ix = 0; ix < height; ++ix, ++to ) { - return mHnd ; + auto from = dataPtr + ix * width + iy; + *to = *from; } + } +} - private: - AutoJpg( const AutoJpg& ); //< not defined - AutoJpg& operator= ( const AutoJpg& ); //< not defined - tjhandle mHnd; - }; // struct AutoJpg; +template +void Rotate90(PixelArray buffer, int width, int height) +{ + using PixelT = PixelType; + Vector data; + data.Resize(width * height); + auto dataPtr = data.Begin(); - /** RAII wrapper to free memory allocated by the jpeg-turbo library. */ - struct AutoJpgMem + auto original = reinterpret_cast(buffer); + std::copy(original, original + width * height, dataPtr); + + std::swap(width, height); + auto hw = width * height; + hw = - hw - 1; + + auto to = original + width - 1; + auto from = dataPtr; + + for(auto ix = width; --ix >= 0;) { - AutoJpgMem(unsigned char * const tjMem) - : mTjMem(tjMem) + for(auto iy = height; --iy >= 0; ++from) { + *to = *from; + to += width; } + to += hw; + } +} - ~AutoJpgMem() - { - tjFree(mTjMem); - } +template +void Rotate180(PixelArray buffer, int width, int height) +{ + using PixelT = PixelType; + Vector data; + data.Resize(width * height); + auto dataPtr = data.Begin(); + + auto original = reinterpret_cast(buffer); + std::copy(original, original + width * height, dataPtr); - unsigned char * Get() const + auto to = original; + for( auto iy = 0; iy < width; iy++ ) + { + for( auto ix = 0; ix < height; ix++ ) { - return mTjMem; + auto from = dataPtr + (height - ix) * width - 1 - iy; + *to = *from; + ++to; } + } +} + + +template +void Rotate270(PixelArray buffer, int width, int height) +{ + using PixelT = PixelType; + Vector data; + data.Resize(width * height); + auto dataPtr = data.Begin(); + + auto original = reinterpret_cast(buffer); + std::copy(original, original + width * height, dataPtr); - private: - AutoJpgMem( const AutoJpgMem& ); //< not defined - AutoJpgMem& operator= ( const AutoJpgMem& ); //< not defined + auto w = height; + std::swap(width, height); + auto hw = width * height; - unsigned char * const mTjMem; - }; + auto* to = original + hw - width; + auto* from = dataPtr; - // Workaround to avoid exceeding the maximum texture size - const int MAX_TEXTURE_WIDTH = 4096; - const int MAX_TEXTURE_HEIGHT = 4096; + w = -w; + hw = hw + 1; + for(auto ix = width; --ix >= 0;) + { + for(auto iy = height; --iy >= 0;) + { + *to = *from; + ++from; + to += w; + } + to += hw; + } +} } // namespace -bool JpegRotate90 (unsigned char *buffer, int width, int height, int bpp); -bool JpegRotate180(unsigned char *buffer, int width, int height, int bpp); -bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp); -JPGFORM_CODE ConvertExifOrientation(ExifData* exifData); -bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transform, +namespace Dali +{ + +namespace TizenPlatform +{ + +JpegTransform ConvertExifOrientation(ExifData* exifData); +bool TransformSize( int requiredWidth, int requiredHeight, + FittingMode::Type fittingMode, SamplingMode::Type samplingMode, + JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight ); @@ -190,7 +505,11 @@ bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height ) return false; } +// jpeg_create_decompress internally uses C casts +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" jpeg_create_decompress( &cinfo ); +#pragma GCC diagnostic pop jpeg_stdio_src( &cinfo, fp ); @@ -209,7 +528,7 @@ bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height ) return true; } -bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader::Input& input, Integration::Bitmap& bitmap ) +bool LoadBitmapFromJpeg( const ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap ) { const int flags= 0; FILE* const fp = input.file; @@ -238,22 +557,22 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader: return false; } - std::vector jpegBuffer(0); + Vector jpegBuffer; try { - jpegBuffer.reserve( jpegBufferSize ); + jpegBuffer.Resize( jpegBufferSize ); } catch(...) { DALI_LOG_ERROR( "Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U ); return false; } - unsigned char * const jpegBufferPtr = &jpegBuffer[0]; + unsigned char * const jpegBufferPtr = jpegBuffer.Begin(); // Pull the compressed JPEG image bytes out of a file and into memory: if( fread( jpegBufferPtr, 1, jpegBufferSize, fp ) != jpegBufferSize ) { - DALI_LOG_WARNING("Error on image file read."); + DALI_LOG_WARNING("Error on image file read.\n"); return false; } @@ -262,40 +581,65 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader: DALI_LOG_ERROR("Error seeking to start of file\n"); } - // Allow early cancellation between the load and the decompress: - client.InterruptionPoint(); - - AutoJpg autoJpg(tjInitDecompress()); + auto jpeg = MakeJpegDecompressor(); - if(autoJpg.GetHandle() == NULL) + if(!jpeg) { DALI_LOG_ERROR("%s\n", tjGetErrorStr()); return false; } - JPGFORM_CODE transform = JPGFORM_NONE; + auto transform = JpegTransform::NONE; - if( input.reorientationRequested ) + // extract exif data + auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize); + + if( exifData && input.reorientationRequested ) { - ExifAutoPtr exifData( exif_data_new_from_data(jpegBufferPtr, jpegBufferSize) ); - if( exifData.mData ) + transform = ConvertExifOrientation(exifData.get()); + } + + std::unique_ptr exifMap; + exifMap.reset( new Property::Map() ); + + for( auto k = 0u; k < EXIF_IFD_COUNT; ++k ) + { + auto content = exifData->ifd[k]; + for (auto i = 0u; i < content->count; ++i) { - transform = ConvertExifOrientation(exifData.mData); + auto &&tag = content->entries[i]; + const char *shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast(k)); + if(shortName) + { + AddExifFieldPropertyMap(*exifMap, *tag, static_cast(k)); + } } } // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array: int chrominanceSubsampling = -1; int preXformImageWidth = 0, preXformImageHeight = 0; - if( tjDecompressHeader2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling ) == -1 ) + + // In Ubuntu, the turbojpeg version is not correct. so build error occurs. + // Temporarily separate Ubuntu and other profiles. +#ifndef DALI_PROFILE_UBUNTU + int jpegColorspace = -1; + if( tjDecompressHeader3( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace ) == -1 ) + { + DALI_LOG_ERROR("%s\n", tjGetErrorStr()); + // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type. + } +#else + if( tjDecompressHeader2( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling ) == -1 ) { DALI_LOG_ERROR("%s\n", tjGetErrorStr()); // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type. } +#endif if(preXformImageWidth == 0 || preXformImageHeight == 0) { - DALI_LOG_WARNING("Invalid Image!"); + DALI_LOG_WARNING("Invalid Image!\n"); return false; } @@ -314,191 +658,170 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader: int scaledPostXformWidth = postXformImageWidth; int scaledPostXformHeight = postXformImageHeight; - TransformSize( requiredWidth, requiredHeight, transform, + TransformSize( requiredWidth, requiredHeight, + input.scalingParameters.scalingMode, + input.scalingParameters.samplingMode, + transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight ); + + // Colorspace conversion options + TJPF pixelLibJpegType = TJPF_RGB; + Pixel::Format pixelFormat = Pixel::RGB888; +#ifndef DALI_PROFILE_UBUNTU + switch (jpegColorspace) + { + case TJCS_RGB: + // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission. + // YCbCr images must be converted to RGB before they can actually be displayed. + case TJCS_YCbCr: + { + pixelLibJpegType = TJPF_RGB; + pixelFormat = Pixel::RGB888; + break; + } + case TJCS_GRAY: + { + pixelLibJpegType = TJPF_GRAY; + pixelFormat = Pixel::L8; + break; + } + case TJCS_CMYK: + case TJCS_YCCK: + { + pixelLibJpegType = TJPF_CMYK; + pixelFormat = Pixel::RGBA8888; + break; + } + default: + { + pixelLibJpegType = TJPF_RGB; + pixelFormat = Pixel::RGB888; + break; + } + } +#endif // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer: + bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat); - unsigned char * const bitmapPixelBuffer = bitmap.GetPackedPixelsProfile()->ReserveBuffer(Pixel::RGB888, scaledPostXformWidth, scaledPostXformHeight); + // set metadata + GetImplementation(bitmap).SetMetadata( std::move(exifMap) ); - // Allow early cancellation before decoding: - client.InterruptionPoint(); + auto bitmapPixelBuffer = bitmap.GetBuffer(); - const int pitch = scaledPreXformWidth * DECODED_PIXEL_SIZE; - if( tjDecompress2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, pitch, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 ) + if( tjDecompress2( jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast( bitmapPixelBuffer ), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags ) == -1 ) { - DALI_LOG_ERROR("%s\n", tjGetErrorStr()); - return false; + std::string errorString = tjGetErrorStr(); + + if( IsJpegErrorFatal( errorString ) ) + { + DALI_LOG_ERROR("%s\n", errorString.c_str()); + return false; + } + else + { + DALI_LOG_WARNING("%s\n", errorString.c_str()); + } } const unsigned int bufferWidth = GetTextureDimension( scaledPreXformWidth ); const unsigned int bufferHeight = GetTextureDimension( scaledPreXformHeight ); - if( transform != JPGFORM_NONE ) - { - // Allow early cancellation before shuffling pixels around on the CPU: - client.InterruptionPoint(); - } - bool result = false; switch(transform) { - case JPGFORM_FLIP_H: - case JPGFORM_FLIP_V: - case JPGFORM_TRANSPOSE: - case JPGFORM_TRANSVERSE: ///!ToDo: None of these are supported! - { - DALI_LOG_WARNING( "Unsupported JPEG Orientation transformation: %x.\n", transform ); - break; - } - case JPGFORM_NONE: + case JpegTransform::NONE: { result = true; break; } - case JPGFORM_ROT_180: + // 3 orientation changes for a camera held perpendicular to the ground or upside-down: + case JpegTransform::ROTATE_180: { - result = JpegRotate180(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE); + static auto rotate180Functions = TransformFunctionArray { + &Rotate180<1>, + &Rotate180<3>, + &Rotate180<4>, + }; + result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat ); break; } - case JPGFORM_ROT_270: + case JpegTransform::ROTATE_270: { - result = JpegRotate270(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE); + static auto rotate270Functions = TransformFunctionArray { + &Rotate270<1>, + &Rotate270<3>, + &Rotate270<4>, + }; + result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat ); break; } - case JPGFORM_ROT_90: + case JpegTransform::ROTATE_90: { - result = JpegRotate90(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE); + static auto rotate90Functions = TransformFunctionArray { + &Rotate90<1>, + &Rotate90<3>, + &Rotate90<4>, + }; + result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat ); break; } - } - return result; -} - -///@Todo: Move all these rotation functions to portable/image-operations and take "Jpeg" out of their names. -bool JpegRotate90(unsigned char *buffer, int width, int height, int bpp) -{ - int w, iw, ih, hw = 0; - int ix, iy = 0; - iw = width; - ih = height; - std::vector data(width * height * bpp); - unsigned char *dataPtr = &data[0]; - memcpy(dataPtr, buffer, width * height * bpp); - w = ih; - ih = iw; - iw = w; - hw = iw * ih; - hw = - hw - 1; - switch(bpp) - { - case 3: + case JpegTransform::FLIP_VERTICAL: { - RGB888Type* to = reinterpret_cast(buffer) + iw - 1; - RGB888Type* from = reinterpret_cast( dataPtr ); - - for(ix = iw; -- ix >= 0;) - { - for(iy = ih; -- iy >= 0; ++from ) - { - *to = *from; - to += iw; - } - to += hw; - } + static auto flipVerticalFunctions = TransformFunctionArray { + &FlipVertical<1>, + &FlipVertical<3>, + &FlipVertical<4>, + }; + result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat ); break; } - - default: - { - return false; - } - } - - return true; -} - -bool JpegRotate180(unsigned char *buffer, int width, int height, int bpp) -{ - int ix, iw, ih, hw = 0; - iw = width; - ih = height; - hw = iw * ih; - ix = hw; - - switch(bpp) - { - case 3: + // Less-common orientation changes, since they don't correspond to a camera's physical orientation: + case JpegTransform::FLIP_HORIZONTAL: { - RGB888Type tmp; - RGB888Type* to = reinterpret_cast(buffer) ; - RGB888Type* from = reinterpret_cast( buffer ) + hw - 1; - for(; --ix >= (hw / 2); ++to, --from) - { - tmp = *to; - *to = *from; - *from = tmp; - } + static auto flipHorizontalFunctions = TransformFunctionArray { + &FlipHorizontal<1>, + &FlipHorizontal<3>, + &FlipHorizontal<4>, + }; + result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat ); break; } - - default: + case JpegTransform::TRANSPOSE: { - return false; + static auto transposeFunctions = TransformFunctionArray { + &Transpose<1>, + &Transpose<3>, + &Transpose<4>, + }; + result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat ); + break; } - } - - return true; -} - -bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp) -{ - int w, iw, ih, hw = 0; - int ix, iy = 0; - - iw = width; - ih = height; - std::vector data(width * height * bpp); - unsigned char *dataPtr = &data[0]; - memcpy(dataPtr, buffer, width * height * bpp); - w = ih; - ih = iw; - iw = w; - hw = iw * ih; - - switch(bpp) - { - case 3: + case JpegTransform::TRANSVERSE: { - RGB888Type* to = reinterpret_cast(buffer) + hw - iw; - RGB888Type* from = reinterpret_cast( dataPtr ); - - w = -w; - hw = hw + 1; - for(ix = iw; -- ix >= 0;) - { - for(iy = ih; -- iy >= 0;) - { - *to = *from; - from += 1; - to += w; - } - to += hw; - } + static auto transverseFunctions = TransformFunctionArray { + &Transverse<1>, + &Transverse<3>, + &Transverse<4>, + }; + result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat ); break; } default: { - return false; + DALI_LOG_ERROR( "Unsupported JPEG Orientation transformation: %x.\n", transform ); + break; } } - return true; + return result; } -bool EncodeToJpeg( const unsigned char* const pixelBuffer, std::vector< unsigned char >& encodedPixels, const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality) +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 ) { + if( !pixelBuffer ) { DALI_LOG_ERROR("Null input buffer\n"); @@ -529,7 +852,7 @@ bool EncodeToJpeg( const unsigned char* const pixelBuffer, std::vector< unsigned } default: { - DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG." ); + DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG.\n" ); return false; } } @@ -547,39 +870,44 @@ bool EncodeToJpeg( const unsigned char* const pixelBuffer, std::vector< unsigned } // Initialise a JPEG codec: - AutoJpg autoJpg( tjInitCompress() ); { - if( autoJpg.GetHandle() == NULL ) + auto jpeg = MakeJpegCompressor(); + if( jpeg ) { DALI_LOG_ERROR( "JPEG Compressor init failed: %s\n", tjGetErrorStr() ); return false; } + + // Safely wrap the jpeg codec's buffer in case we are about to throw, then + // save the pixels to a persistent buffer that we own and let our cleaner + // class clean up the buffer as it goes out of scope: + auto dstBuffer = MakeJpegMemory(); + // Run the compressor: - unsigned char* dstBuffer = NULL; unsigned long dstBufferSize = 0; const int flags = 0; - if( tjCompress2( autoJpg.GetHandle(), const_cast(pixelBuffer), width, 0, height, jpegPixelFormat, &dstBuffer, &dstBufferSize, TJSAMP_444, quality, flags ) ) + if( tjCompress2( jpeg.get(), + const_cast(pixelBuffer), + width, 0, height, + jpegPixelFormat, SetPointer(dstBuffer), &dstBufferSize, + TJSAMP_444, quality, flags ) ) { DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr()); return false; } - // Safely wrap the jpeg codec's buffer in case we are about to throw, then - // save the pixels to a persistent buffer that we own and let our cleaner - // class clean up the buffer as it goes out of scope: - AutoJpgMem cleaner(dstBuffer); - encodedPixels.resize(dstBufferSize); - memcpy(&encodedPixels[0], dstBuffer, dstBufferSize); + encodedPixels.Resize( dstBufferSize ); + memcpy( encodedPixels.Begin(), dstBuffer.get(), dstBufferSize ); } return true; } -JPGFORM_CODE ConvertExifOrientation(ExifData* exifData) +JpegTransform ConvertExifOrientation(ExifData* exifData) { - JPGFORM_CODE transform = JPGFORM_NONE; + auto transform = JpegTransform::NONE; ExifEntry * const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION); int orientation = 0; if( entry ) @@ -589,48 +917,48 @@ JPGFORM_CODE ConvertExifOrientation(ExifData* exifData) { case 1: { - transform = JPGFORM_NONE; + transform = JpegTransform::NONE; break; } case 2: { - transform = JPGFORM_FLIP_H; + transform = JpegTransform::FLIP_HORIZONTAL; break; } case 3: { - transform = JPGFORM_FLIP_V; + transform = JpegTransform::FLIP_VERTICAL; break; } case 4: { - transform = JPGFORM_TRANSPOSE; + transform = JpegTransform::TRANSPOSE; break; } case 5: { - transform = JPGFORM_TRANSVERSE; + transform = JpegTransform::TRANSVERSE; break; } case 6: { - transform = JPGFORM_ROT_90; + transform = JpegTransform::ROTATE_90; break; } case 7: { - transform = JPGFORM_ROT_180; + transform = JpegTransform::ROTATE_180; break; } case 8: { - transform = JPGFORM_ROT_270; + transform = JpegTransform::ROTATE_270; break; } default: { // Try to keep loading the file, but let app developer know there was something fishy: - DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.", entry ); + DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry ); break; } } @@ -638,18 +966,25 @@ JPGFORM_CODE ConvertExifOrientation(ExifData* exifData) return transform; } -bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transform, +bool TransformSize( int requiredWidth, int requiredHeight, + FittingMode::Type fittingMode, SamplingMode::Type samplingMode, + JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight ) { bool success = true; - if( transform == JPGFORM_ROT_90 || transform == JPGFORM_ROT_270 ) + if( transform == JpegTransform::ROTATE_90 || transform == JpegTransform::ROTATE_270 || transform == JpegTransform::ROTATE_180 || transform == JpegTransform::TRANSVERSE) { std::swap( requiredWidth, requiredHeight ); std::swap( postXformImageWidth, postXformImageHeight ); } + // Apply the special rules for when there are one or two zeros in requested dimensions: + const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions( ImageDimensions( postXformImageWidth, postXformImageHeight), ImageDimensions( requiredWidth, requiredHeight ) ); + requiredWidth = correctedDesired.GetWidth(); + requiredHeight = correctedDesired.GetHeight(); + // Rescale image during decode using one of the decoder's built-in rescaling // ratios (expected to be powers of 2), keeping the final image at least as // wide and high as was requested: @@ -658,47 +993,75 @@ bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transfor tjscalingfactor* factors = tjGetScalingFactors( &numFactors ); if( factors == NULL ) { - DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!"); + DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n"); success = false; } else { - // Find nearest supported scaling factor (factors are in sequential order, getting smaller) - int scaleFactorIndex( 0 ); - for( int i = 1; i < numFactors; ++i ) + // Internal jpeg downscaling is the same as our BOX_X sampling modes so only + // apply it if the application requested one of those: + // (use a switch case here so this code will fail to compile if other modes are added) + bool downscale = true; + switch( samplingMode ) { - // if requested width or height set to 0, ignore value - // TJSCALED performs an integer-based ceil operation on (dim*factor) - if( (requiredWidth && TJSCALED(postXformImageWidth , (factors[i])) > requiredWidth) || - (requiredHeight && TJSCALED(postXformImageHeight, (factors[i])) > requiredHeight) ) + case SamplingMode::BOX: + case SamplingMode::BOX_THEN_NEAREST: + case SamplingMode::BOX_THEN_LINEAR: + case SamplingMode::DONT_CARE: { - scaleFactorIndex = i; + downscale = true; + break; } - else + case SamplingMode::NO_FILTER: + case SamplingMode::NEAREST: + case SamplingMode::LINEAR: { - // This scaling would result in an image that was smaller than requested in both - // dimensions, so stop at the previous entry. + downscale = false; break; } } - // Workaround for libjpeg-turbo problem adding a green line on one edge - // when downscaling to 1/8 in each dimension. Prefer not to scale to less than - // 1/4 in each dimension: - if( 2 < scaleFactorIndex ) + int scaleFactorIndex( 0 ); + if( downscale ) { - scaleFactorIndex = 2; - DALI_LOG_INFO( gLoaderFilter, Debug::General, "Down-scaling requested for image limited to 1/4.\n" ); + // Find nearest supported scaling factor (factors are in sequential order, getting smaller) + for( int i = 1; i < numFactors; ++i ) + { + bool widthLessRequired = TJSCALED( postXformImageWidth, factors[i]) < requiredWidth; + bool heightLessRequired = TJSCALED( postXformImageHeight, factors[i]) < requiredHeight; + // If either scaled dimension is smaller than the desired one, we were done at the last iteration + if ( (fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired) ) + { + break; + } + // If both dimensions are smaller than the desired one, we were done at the last iteration: + if ( (fittingMode == FittingMode::SHRINK_TO_FIT) && ( widthLessRequired && heightLessRequired ) ) + { + break; + } + // If the width is smaller than the desired one, we were done at the last iteration: + if ( fittingMode == FittingMode::FIT_WIDTH && widthLessRequired ) + { + break; + } + // If the width is smaller than the desired one, we were done at the last iteration: + if ( fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired ) + { + break; + } + // This factor stays is within our fitting mode constraint so remember it: + scaleFactorIndex = i; + } } - // Regardless of requested size, downscale to avoid exceeding the maximum texture size + // Regardless of requested size, downscale to avoid exceeding the maximum texture size: for( int i = scaleFactorIndex; i < numFactors; ++i ) { // Continue downscaling to below maximum texture size (if possible) scaleFactorIndex = i; - if( TJSCALED(postXformImageWidth, (factors[i])) < MAX_TEXTURE_WIDTH && - TJSCALED(postXformImageHeight, (factors[i])) < MAX_TEXTURE_HEIGHT ) + if( TJSCALED(postXformImageWidth, (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) && + TJSCALED(postXformImageHeight, (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) ) { // Current scale-factor downscales to below maximum texture size break; @@ -718,10 +1081,9 @@ bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transfor return success; } -ExifData* LoadExifData( FILE* fp ) +ExifHandle LoadExifData( FILE* fp ) { - ExifData* exifData=NULL; - ExifLoader* exifLoader; + auto exifData = MakeNullExifData(); unsigned char dataBuffer[1024]; if( fseek( fp, 0, SEEK_SET ) ) @@ -730,7 +1092,8 @@ ExifData* LoadExifData( FILE* fp ) } else { - exifLoader = exif_loader_new (); + auto exifLoader = std::unique_ptr{ + exif_loader_new(), exif_loader_unref }; while( !feof(fp) ) { @@ -739,14 +1102,13 @@ ExifData* LoadExifData( FILE* fp ) { break; } - if( ! exif_loader_write( exifLoader, dataBuffer, size ) ) + if( ! exif_loader_write( exifLoader.get(), dataBuffer, size ) ) { break; } } - exifData = exif_loader_get_data( exifLoader ); - exif_loader_unref( exifLoader ); + exifData.reset( exif_loader_get_data( exifLoader.get() ) ); } return exifData; @@ -770,14 +1132,14 @@ bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsig unsigned int headerHeight; if( LoadJpegHeader( fp, headerWidth, headerHeight ) ) { - JPGFORM_CODE transform = JPGFORM_NONE; + auto transform = JpegTransform::NONE; if( input.reorientationRequested ) { - ExifAutoPtr exifData( LoadExifData( fp ) ); - if( exifData.mData ) + auto exifData = LoadExifData( fp ); + if( exifData ) { - transform = ConvertExifOrientation(exifData.mData); + transform = ConvertExifOrientation(exifData.get()); } int preXformImageWidth = headerWidth; @@ -785,7 +1147,7 @@ bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsig int postXformImageWidth = headerWidth; int postXformImageHeight = headerHeight; - success = TransformSize(requiredWidth, requiredHeight, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight); + success = TransformSize( requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight ); if(success) { width = postXformImageWidth;