2 * Copyright (c) 2015 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 "loader-jpeg.h"
20 #include "resource-loading-client.h"
21 #include <dali/integration-api/bitmap.h>
22 #include <resource-loader/debug/resource-loader-debug.h>
23 #include "platform-capabilities.h"
24 #include "image-operations.h"
27 #include <libexif/exif-data.h>
28 #include <libexif/exif-loader.h>
29 #include <libexif/exif-tag.h>
30 #include <turbojpeg.h>
37 using Integration::Bitmap;
39 namespace TizenPlatform
44 const unsigned DECODED_PIXEL_SIZE = 3;
45 const TJPF DECODED_PIXEL_LIBJPEG_TYPE = TJPF_RGB;
47 /** Transformations that can be applied to decoded pixels to respect exif orientation
48 * codes in image headers */
51 JPGFORM_NONE = 1, /* no transformation 0th-Row = top & 0th-Column = left */
52 JPGFORM_FLIP_H, /* horizontal flip 0th-Row = top & 0th-Column = right */
53 JPGFORM_FLIP_V, /* vertical flip 0th-Row = bottom & 0th-Column = right*/
54 JPGFORM_TRANSPOSE, /* transpose across UL-to-LR axis 0th-Row = bottom & 0th-Column = left*/
55 JPGFORM_TRANSVERSE,/* transpose across UR-to-LL axis 0th-Row = left & 0th-Column = top*/
56 JPGFORM_ROT_90, /* 90-degree clockwise rotation 0th-Row = right & 0th-Column = top*/
57 JPGFORM_ROT_180, /* 180-degree rotation 0th-Row = right & 0th-Column = bottom*/
58 JPGFORM_ROT_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.
72 struct JpegErrorState {
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 ) )
111 /** Simple struct to ensure xif data is deleted. */
114 ExifAutoPtr( ExifData* data)
120 exif_data_free( mData);
125 /** simple class to enforce clean-up of JPEG structures. */
128 AutoJpg(const tjhandle jpgHandle)
135 // clean up JPG resources
139 tjhandle GetHandle() const
145 AutoJpg( const AutoJpg& ); //< not defined
146 AutoJpg& operator= ( const AutoJpg& ); //< not defined
149 }; // struct AutoJpg;
151 /** RAII wrapper to free memory allocated by the jpeg-turbo library. */
154 AutoJpgMem(unsigned char * const tjMem)
164 unsigned char * Get() const
170 AutoJpgMem( const AutoJpgMem& ); //< not defined
171 AutoJpgMem& operator= ( const AutoJpgMem& ); //< not defined
173 unsigned char * const mTjMem;
176 // Workaround to avoid exceeding the maximum texture size
177 const int MAX_TEXTURE_WIDTH = 4096;
178 const int MAX_TEXTURE_HEIGHT = 4096;
182 bool JpegRotate90 (unsigned char *buffer, int width, int height, int bpp);
183 bool JpegRotate180(unsigned char *buffer, int width, int height, int bpp);
184 bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp);
185 JPGFORM_CODE ConvertExifOrientation(ExifData* exifData);
186 bool TransformSize( int requiredWidth, int requiredHeight,
187 FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
188 JPGFORM_CODE transform,
189 int& preXformImageWidth, int& preXformImageHeight,
190 int& postXformImageWidth, int& postXformImageHeight );
192 bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height )
194 // using libjpeg API to avoid having to read the whole file in a buffer
195 struct jpeg_decompress_struct cinfo;
196 struct JpegErrorState jerr;
197 cinfo.err = jpeg_std_error( &jerr.errorManager );
199 jerr.errorManager.output_message = JpegOutputMessageHandler;
200 jerr.errorManager.error_exit = JpegErrorHandler;
202 // On error exit from the JPEG lib, control will pass via JpegErrorHandler
203 // into this branch body for cleanup and error return:
204 if(setjmp(jerr.jumpBuffer))
206 jpeg_destroy_decompress(&cinfo);
210 jpeg_create_decompress( &cinfo );
212 jpeg_stdio_src( &cinfo, fp );
214 // Check header to see if it is JPEG file
215 if( jpeg_read_header( &cinfo, TRUE ) != JPEG_HEADER_OK )
218 jpeg_destroy_decompress( &cinfo );
222 width = cinfo.image_width;
223 height = cinfo.image_height;
225 jpeg_destroy_decompress( &cinfo );
229 bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader::Input& input, Integration::Bitmap& bitmap )
232 FILE* const fp = input.file;
234 if( fseek(fp,0,SEEK_END) )
236 DALI_LOG_ERROR("Error seeking to end of file\n");
240 long positionIndicator = ftell(fp);
241 unsigned int jpegBufferSize = 0u;
242 if( positionIndicator > -1L )
244 jpegBufferSize = static_cast<unsigned int>(positionIndicator);
247 if( 0u == jpegBufferSize )
252 if( fseek(fp, 0, SEEK_SET) )
254 DALI_LOG_ERROR("Error seeking to start of file\n");
258 Vector<unsigned char> jpegBuffer;
261 jpegBuffer.Resize( jpegBufferSize );
265 DALI_LOG_ERROR( "Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U );
268 unsigned char * const jpegBufferPtr = jpegBuffer.Begin();
270 // Pull the compressed JPEG image bytes out of a file and into memory:
271 if( fread( jpegBufferPtr, 1, jpegBufferSize, fp ) != jpegBufferSize )
273 DALI_LOG_WARNING("Error on image file read.\n");
277 if( fseek(fp, 0, SEEK_SET) )
279 DALI_LOG_ERROR("Error seeking to start of file\n");
282 // Allow early cancellation between the load and the decompress:
283 client.InterruptionPoint();
285 AutoJpg autoJpg(tjInitDecompress());
287 if(autoJpg.GetHandle() == NULL)
289 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
293 JPGFORM_CODE transform = JPGFORM_NONE;
295 if( input.reorientationRequested )
297 ExifAutoPtr exifData( exif_data_new_from_data(jpegBufferPtr, jpegBufferSize) );
300 transform = ConvertExifOrientation(exifData.mData);
304 // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
305 int chrominanceSubsampling = -1;
306 int preXformImageWidth = 0, preXformImageHeight = 0;
307 if( tjDecompressHeader2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling ) == -1 )
309 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
310 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
313 if(preXformImageWidth == 0 || preXformImageHeight == 0)
315 DALI_LOG_WARNING("Invalid Image!\n");
319 int requiredWidth = input.scalingParameters.dimensions.GetWidth();
320 int requiredHeight = input.scalingParameters.dimensions.GetHeight();
322 // If transform is a 90 or 270 degree rotation, the logical width and height
323 // request from the client needs to be adjusted to account by effectively
324 // rotating that too, and the final width and height need to be swapped:
325 int postXformImageWidth = preXformImageWidth;
326 int postXformImageHeight = preXformImageHeight;
329 int scaledPreXformWidth = preXformImageWidth;
330 int scaledPreXformHeight = preXformImageHeight;
331 int scaledPostXformWidth = postXformImageWidth;
332 int scaledPostXformHeight = postXformImageHeight;
334 TransformSize( requiredWidth, requiredHeight,
335 input.scalingParameters.scalingMode,
336 input.scalingParameters.samplingMode,
338 scaledPreXformWidth, scaledPreXformHeight,
339 scaledPostXformWidth, scaledPostXformHeight );
341 // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
343 unsigned char * const bitmapPixelBuffer = bitmap.GetPackedPixelsProfile()->ReserveBuffer(Pixel::RGB888, scaledPostXformWidth, scaledPostXformHeight);
345 // Allow early cancellation before decoding:
346 client.InterruptionPoint();
348 if( tjDecompress2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, 0, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 )
350 std::string errorString = tjGetErrorStr();
352 if( IsJpegErrorFatal( errorString ) )
354 DALI_LOG_ERROR("%s\n", errorString.c_str());
359 DALI_LOG_WARNING("%s\n", errorString.c_str());
363 const unsigned int bufferWidth = GetTextureDimension( scaledPreXformWidth );
364 const unsigned int bufferHeight = GetTextureDimension( scaledPreXformHeight );
366 if( transform != JPGFORM_NONE )
368 // Allow early cancellation before shuffling pixels around on the CPU:
369 client.InterruptionPoint();
380 // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
381 case JPGFORM_ROT_180:
383 result = JpegRotate180(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
386 case JPGFORM_ROT_270:
388 result = JpegRotate270(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
393 result = JpegRotate90(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
396 /// Less-common orientation changes, since they don't correspond to a camera's
397 // physical orientation:
400 case JPGFORM_TRANSPOSE:
401 case JPGFORM_TRANSVERSE:
403 DALI_LOG_WARNING( "Unsupported JPEG Orientation transformation: %x.\n", transform );
410 ///@Todo: Move all these rotation functions to portable/image-operations and take "Jpeg" out of their names.
411 bool JpegRotate90(unsigned char *buffer, int width, int height, int bpp)
413 int w, iw, ih, hw = 0;
417 Vector<unsigned char> data;
418 data.Resize(width * height * bpp);
419 unsigned char *dataPtr = data.Begin();
420 memcpy(dataPtr, buffer, width * height * bpp);
430 RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) + iw - 1;
431 RGB888Type* from = reinterpret_cast<RGB888Type*>( dataPtr );
433 for(ix = iw; -- ix >= 0;)
435 for(iy = ih; -- iy >= 0; ++from )
454 bool JpegRotate180(unsigned char *buffer, int width, int height, int bpp)
456 int ix, iw, ih, hw = 0;
467 RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) ;
468 RGB888Type* from = reinterpret_cast<RGB888Type*>( buffer ) + hw - 1;
469 for(; --ix >= (hw / 2); ++to, --from)
487 bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp)
489 int w, iw, ih, hw = 0;
494 Vector<unsigned char> data;
495 data.Resize(width * height * bpp);
496 unsigned char *dataPtr = data.Begin();
497 memcpy(dataPtr, buffer, width * height * bpp);
507 RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) + hw - iw;
508 RGB888Type* from = reinterpret_cast<RGB888Type*>( dataPtr );
512 for(ix = iw; -- ix >= 0;)
514 for(iy = ih; -- iy >= 0;)
533 bool EncodeToJpeg( const unsigned char* const pixelBuffer, Vector< unsigned char >& encodedPixels,
534 const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality )
539 DALI_LOG_ERROR("Null input buffer\n");
543 // Translate pixel format enum:
544 int jpegPixelFormat = -1;
546 switch( pixelFormat )
550 jpegPixelFormat = TJPF_RGB;
553 case Pixel::RGBA8888:
556 jpegPixelFormat = TJPF_RGBX;
559 case Pixel::BGRA8888:
562 jpegPixelFormat = TJPF_BGRX;
567 DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG.\n" );
572 // Assert quality is in the documented allowable range of the jpeg-turbo lib:
573 DALI_ASSERT_DEBUG( quality >= 1 );
574 DALI_ASSERT_DEBUG( quality <= 100 );
584 // Initialise a JPEG codec:
585 AutoJpg autoJpg( tjInitCompress() );
587 if( autoJpg.GetHandle() == NULL )
589 DALI_LOG_ERROR( "JPEG Compressor init failed: %s\n", tjGetErrorStr() );
593 // Run the compressor:
594 unsigned char* dstBuffer = NULL;
595 unsigned long dstBufferSize = 0;
598 if( tjCompress2( autoJpg.GetHandle(), const_cast<unsigned char*>(pixelBuffer), width, 0, height, jpegPixelFormat, &dstBuffer, &dstBufferSize, TJSAMP_444, quality, flags ) )
600 DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
604 // Safely wrap the jpeg codec's buffer in case we are about to throw, then
605 // save the pixels to a persistent buffer that we own and let our cleaner
606 // class clean up the buffer as it goes out of scope:
607 AutoJpgMem cleaner( dstBuffer );
608 encodedPixels.Resize( dstBufferSize );
609 memcpy( encodedPixels.Begin(), dstBuffer, dstBufferSize );
615 JPGFORM_CODE ConvertExifOrientation(ExifData* exifData)
617 JPGFORM_CODE transform = JPGFORM_NONE;
618 ExifEntry * const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
622 orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
623 switch( orientation )
627 transform = JPGFORM_NONE;
632 transform = JPGFORM_FLIP_H;
637 transform = JPGFORM_FLIP_V;
642 transform = JPGFORM_TRANSPOSE;
647 transform = JPGFORM_TRANSVERSE;
652 transform = JPGFORM_ROT_90;
657 transform = JPGFORM_ROT_180;
662 transform = JPGFORM_ROT_270;
667 // Try to keep loading the file, but let app developer know there was something fishy:
668 DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry );
676 bool TransformSize( int requiredWidth, int requiredHeight,
677 FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
678 JPGFORM_CODE transform,
679 int& preXformImageWidth, int& preXformImageHeight,
680 int& postXformImageWidth, int& postXformImageHeight )
684 if( transform == JPGFORM_ROT_90 || transform == JPGFORM_ROT_270 )
686 std::swap( requiredWidth, requiredHeight );
687 std::swap( postXformImageWidth, postXformImageHeight );
690 // Apply the special rules for when there are one or two zeros in requested dimensions:
691 const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions( ImageDimensions( postXformImageWidth, postXformImageHeight), ImageDimensions( requiredWidth, requiredHeight ) );
692 requiredWidth = correctedDesired.GetWidth();
693 requiredHeight = correctedDesired.GetHeight();
695 // Rescale image during decode using one of the decoder's built-in rescaling
696 // ratios (expected to be powers of 2), keeping the final image at least as
697 // wide and high as was requested:
700 tjscalingfactor* factors = tjGetScalingFactors( &numFactors );
701 if( factors == NULL )
703 DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
708 // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
709 // apply it if the application requested one of those:
710 // (use a switch case here so this code will fail to compile if other modes are added)
711 bool downscale = true;
712 switch( samplingMode )
714 case SamplingMode::BOX:
715 case SamplingMode::BOX_THEN_NEAREST:
716 case SamplingMode::BOX_THEN_LINEAR:
717 case SamplingMode::DONT_CARE:
722 case SamplingMode::NO_FILTER:
723 case SamplingMode::NEAREST:
724 case SamplingMode::LINEAR:
731 int scaleFactorIndex( 0 );
734 // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
735 for( int i = 1; i < numFactors; ++i )
737 bool widthLessRequired = TJSCALED( postXformImageWidth, factors[i]) < requiredWidth;
738 bool heightLessRequired = TJSCALED( postXformImageHeight, factors[i]) < requiredHeight;
739 // If either scaled dimension is smaller than the desired one, we were done at the last iteration
740 if ( (fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired) )
744 // If both dimensions are smaller than the desired one, we were done at the last iteration:
745 if ( (fittingMode == FittingMode::SHRINK_TO_FIT) && ( widthLessRequired && heightLessRequired ) )
749 // If the width is smaller than the desired one, we were done at the last iteration:
750 if ( fittingMode == FittingMode::FIT_WIDTH && widthLessRequired )
754 // If the width is smaller than the desired one, we were done at the last iteration:
755 if ( fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired )
759 // This factor stays is within our fitting mode constraint so remember it:
760 scaleFactorIndex = i;
764 // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
765 for( int i = scaleFactorIndex; i < numFactors; ++i )
767 // Continue downscaling to below maximum texture size (if possible)
768 scaleFactorIndex = i;
770 if( TJSCALED(postXformImageWidth, (factors[i])) < MAX_TEXTURE_WIDTH &&
771 TJSCALED(postXformImageHeight, (factors[i])) < MAX_TEXTURE_HEIGHT )
773 // Current scale-factor downscales to below maximum texture size
778 // We have finally chosen the scale-factor, return width/height values
779 if( scaleFactorIndex > 0 )
781 preXformImageWidth = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
782 preXformImageHeight = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
783 postXformImageWidth = TJSCALED(postXformImageWidth, (factors[scaleFactorIndex]));
784 postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
791 ExifData* LoadExifData( FILE* fp )
793 ExifData* exifData=NULL;
794 ExifLoader* exifLoader;
795 unsigned char dataBuffer[1024];
797 if( fseek( fp, 0, SEEK_SET ) )
799 DALI_LOG_ERROR("Error seeking to start of file\n");
803 exifLoader = exif_loader_new ();
807 int size = fread( dataBuffer, 1, sizeof( dataBuffer ), fp );
812 if( ! exif_loader_write( exifLoader, dataBuffer, size ) )
818 exifData = exif_loader_get_data( exifLoader );
819 exif_loader_unref( exifLoader );
825 bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsigned int& height )
827 unsigned int requiredWidth = input.scalingParameters.dimensions.GetWidth();
828 unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
829 FILE* const fp = input.file;
831 bool success = false;
832 if( requiredWidth == 0 && requiredHeight == 0 )
834 success = LoadJpegHeader( fp, width, height );
838 // Double check we get the same width/height from the header
839 unsigned int headerWidth;
840 unsigned int headerHeight;
841 if( LoadJpegHeader( fp, headerWidth, headerHeight ) )
843 JPGFORM_CODE transform = JPGFORM_NONE;
845 if( input.reorientationRequested )
847 ExifAutoPtr exifData( LoadExifData( fp ) );
850 transform = ConvertExifOrientation(exifData.mData);
853 int preXformImageWidth = headerWidth;
854 int preXformImageHeight = headerHeight;
855 int postXformImageWidth = headerWidth;
856 int postXformImageHeight = headerHeight;
858 success = TransformSize( requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight );
861 width = postXformImageWidth;
862 height = postXformImageHeight;
869 height = headerHeight;
877 } // namespace TizenPlatform