/*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2015 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.
#include "loader-jpeg.h"
#include "resource-loading-client.h"
#include <dali/integration-api/bitmap.h>
-#include <dali/public-api/images/image-attributes.h>
#include <resource-loader/debug/resource-loader-debug.h>
#include "platform-capabilities.h"
+#include "image-operations.h"
// EXTERNAL HEADERS
#include <libexif/exif-data.h>
#include <cstring>
#include <setjmp.h>
-
namespace Dali
{
using Integration::Bitmap;
char B;
};
- struct RGBA8888Type
- {
- char R;
- char G;
- char B;
- char A;
- };
-
- struct RGB565Type
- {
- char RG;
- char GB;
- };
-
- struct L8Type
- {
- char gray;
- };
-
/**
* @brief Error handling bookeeping for the JPEG Turbo library's
* setjmp/longjmp simulated exceptions.
/* 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 ) )
+ {
+ return false;
+ }
+ return true;
+ }
+
+
/** Simple struct to ensure xif data is deleted. */
struct ExifAutoPtr
{
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,
+bool TransformSize( int requiredWidth, int requiredHeight,
+ FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
+ JPGFORM_CODE transform,
int& preXformImageWidth, int& preXformImageHeight,
int& postXformImageWidth, int& postXformImageHeight );
return true;
}
-bool LoadBitmapFromJpeg( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
+bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader::Input& input, Integration::Bitmap& bitmap )
{
const int flags= 0;
+ FILE* const fp = input.file;
if( fseek(fp,0,SEEK_END) )
{
return false;
}
- std::vector<unsigned char> jpegBuffer(0);
+ Vector<unsigned char> 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;
}
JPGFORM_CODE transform = JPGFORM_NONE;
- if( attributes.GetOrientationCorrection() )
+ if( input.reorientationRequested )
{
ExifAutoPtr exifData( exif_data_new_from_data(jpegBufferPtr, jpegBufferSize) );
if( exifData.mData )
if(preXformImageWidth == 0 || preXformImageHeight == 0)
{
- DALI_LOG_WARNING("Invalid Image!");
+ DALI_LOG_WARNING("Invalid Image!\n");
return false;
}
- int requiredWidth = attributes.GetWidth();
- int requiredHeight = attributes.GetHeight();
+ int requiredWidth = input.scalingParameters.dimensions.GetWidth();
+ int requiredHeight = input.scalingParameters.dimensions.GetHeight();
// If transform is a 90 or 270 degree rotation, the logical width and height
// request from the client needs to be adjusted to account by effectively
int scaledPostXformWidth = postXformImageWidth;
int scaledPostXformHeight = postXformImageHeight;
- TransformSize( requiredWidth, requiredHeight, transform,
+ TransformSize( requiredWidth, requiredHeight,
+ input.scalingParameters.scalingMode,
+ input.scalingParameters.samplingMode,
+ transform,
scaledPreXformWidth, scaledPreXformHeight,
scaledPostXformWidth, scaledPostXformHeight );
// Allow early cancellation before decoding:
client.InterruptionPoint();
- 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( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, 0, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 )
{
- DALI_LOG_ERROR("%s\n", tjGetErrorStr());
- return false;
- }
+ std::string errorString = tjGetErrorStr();
- attributes.SetSize( scaledPostXformWidth, scaledPostXformHeight );
+ 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 );
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:
{
result = true;
break;
}
+ // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
case JPGFORM_ROT_180:
{
result = JpegRotate180(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
result = JpegRotate90(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
break;
}
+ /// Less-common orientation changes, since they don't correspond to a camera's
+ // physical orientation:
+ case JPGFORM_FLIP_H:
+ case JPGFORM_FLIP_V:
+ case JPGFORM_TRANSPOSE:
+ case JPGFORM_TRANSVERSE:
+ {
+ DALI_LOG_WARNING( "Unsupported JPEG Orientation transformation: %x.\n", transform );
+ 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<unsigned char> data(width * height * bpp);
- unsigned char *dataPtr = &data[0];
+ Vector<unsigned char> data;
+ data.Resize(width * height * bpp);
+ unsigned char *dataPtr = data.Begin();
memcpy(dataPtr, buffer, width * height * bpp);
w = ih;
ih = iw;
iw = width;
ih = height;
- std::vector<unsigned char> data(width * height * bpp);
- unsigned char *dataPtr = &data[0];
+ Vector<unsigned char> data;
+ data.Resize(width * height * bpp);
+ unsigned char *dataPtr = data.Begin();
memcpy(dataPtr, buffer, width * height * bpp);
w = ih;
ih = iw;
return true;
}
-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");
}
default:
{
- DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG." );
+ DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG.\n" );
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);
+ AutoJpgMem cleaner( dstBuffer );
+ encodedPixels.Resize( dstBufferSize );
+ memcpy( encodedPixels.Begin(), dstBuffer, dstBufferSize );
}
return true;
}
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;
}
}
return transform;
}
-bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transform,
+bool TransformSize( int requiredWidth, int requiredHeight,
+ FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
+ JPGFORM_CODE transform,
int& preXformImageWidth, int& preXformImageHeight,
int& postXformImageWidth, int& postXformImageHeight )
{
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:
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)
return exifData;
}
-bool LoadJpegHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &width, unsigned int &height)
+bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsigned int& height )
{
- unsigned int requiredWidth = attributes.GetWidth();
- unsigned int requiredHeight = attributes.GetHeight();
+ unsigned int requiredWidth = input.scalingParameters.dimensions.GetWidth();
+ unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
+ FILE* const fp = input.file;
bool success = false;
if( requiredWidth == 0 && requiredHeight == 0 )
{
JPGFORM_CODE transform = JPGFORM_NONE;
- if( attributes.GetOrientationCorrection() )
+ if( input.reorientationRequested )
{
ExifAutoPtr exifData( LoadExifData( fp ) );
if( exifData.mData )
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;