/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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 <dali/public-api/object/property-map.h>
// INTERNAL HEADERS
+#include <dali/devel-api/adaptor-framework/environment-variable.h>
#include <dali/devel-api/adaptor-framework/image-loading.h>
#include <dali/internal/imaging/common/image-operations.h>
#include <dali/internal/imaging/common/pixel-buffer-impl.h>
{
using Dali::Vector;
namespace Pixel = Dali::Pixel;
-using PixelArray = unsigned char*;
+using PixelArray = uint8_t*;
const unsigned int DECODED_L8 = 1;
const unsigned int DECODED_RGB888 = 3;
const unsigned int DECODED_RGBA8888 = 4;
+const char* CHROMINANCE_SUBSAMPLING_OPTIONS_ENV[] = {"DALI_ENABLE_DECODE_JPEG_TO_YUV_444",
+ "DALI_ENABLE_DECODE_JPEG_TO_YUV_422",
+ "DALI_ENABLE_DECODE_JPEG_TO_YUV_420",
+ "",
+ "DALI_ENABLE_DECODE_JPEG_TO_YUV_440",
+ "DALI_ENABLE_DECODE_JPEG_TO_YUV_411"};
+
+static bool gSubsamplingFormatTable[TJ_NUMSAMP] = {
+ false,
+};
+static bool gIsSubsamplingFormatTableInitialized = false;
+
/** Transformations that can be applied to decoded pixels to respect exif orientation
* codes in image headers */
enum class JpegTransform
jmp_buf jumpBuffer;
};
+static bool IsSubsamplingFormatEnabled(int chrominanceSubsampling)
+{
+ if(!gIsSubsamplingFormatTableInitialized)
+ {
+ for(int i = 0; i < TJ_NUMSAMP; i++)
+ {
+ auto valueString = Dali::EnvironmentVariable::GetEnvironmentVariable(CHROMINANCE_SUBSAMPLING_OPTIONS_ENV[i]);
+ gSubsamplingFormatTable[i] = valueString ? std::atoi(valueString) : false;
+ }
+
+ gIsSubsamplingFormatTableInitialized = true;
+ }
+
+ return gSubsamplingFormatTable[chrominanceSubsampling];
+}
+
/**
* @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.
return true;
}
+bool IsJpegDecodingFailed()
+{
+ std::string errorString = tjGetErrorStr();
+
+ if(DALI_UNLIKELY(IsJpegErrorFatal(errorString)))
+ {
+ DALI_LOG_ERROR("%s\n", errorString.c_str());
+ return true;
+ }
+ else
+ {
+ DALI_LOG_WARNING("%s\n", errorString.c_str());
+ return false;
+ }
+}
+
// helpers for safe exif memory handling
using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
return ExifHandle{nullptr, exif_data_free};
}
-ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size)
+ExifHandle MakeExifDataFromData(uint8_t* data, unsigned int size)
{
return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
}
return JpegHandle{tjInitDecompress(), tjDestroy};
}
-using JpegMemoryHandle = std::unique_ptr<unsigned char, decltype(tjFree)*>;
+using JpegMemoryHandle = std::unique_ptr<uint8_t, decltype(tjFree)*>;
JpegMemoryHandle MakeJpegMemory()
{
{
using PixelT = PixelType<N>;
Vector<PixelT> data;
- data.Resize(width * height);
+ data.ResizeUninitialized(width * height);
auto dataPtr = data.Begin();
auto original = reinterpret_cast<PixelT*>(buffer);
{
using PixelT = PixelType<N>;
Vector<PixelT> data;
- data.Resize(width * height);
+ data.ResizeUninitialized(width * height);
auto dataPtr = data.Begin();
auto original = reinterpret_cast<PixelT*>(buffer);
{
using PixelT = PixelType<N>;
Vector<PixelT> data;
- data.Resize(width * height);
+ data.ResizeUninitialized(width * height);
auto dataPtr = data.Begin();
auto original = reinterpret_cast<PixelT*>(buffer);
{
using PixelT = PixelType<N>;
Vector<PixelT> data;
- data.Resize(width * height);
+ data.ResizeUninitialized(width * height);
auto dataPtr = data.Begin();
auto original = reinterpret_cast<PixelT*>(buffer);
}
}
+void GetJpegPixelFormat(int jpegColorspace, TJPF& pixelLibJpegType, Pixel::Format& pixelFormat)
+{
+ pixelLibJpegType = TJPF_RGB;
+ pixelFormat = Pixel::RGB888;
+
+ 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::RGB888;
+ break;
+ }
+ default:
+ {
+ pixelLibJpegType = TJPF_RGB;
+ pixelFormat = Pixel::RGB888;
+ break;
+ }
+ }
+}
+
+bool TransformBitmap(int scaledPreXformWidth, int scaledPreXformHeight, JpegTransform transform, uint8_t* bitmapPixelBuffer, Pixel::Format pixelFormat)
+{
+ const unsigned int bufferWidth = Dali::TizenPlatform::GetTextureDimension(scaledPreXformWidth);
+ const unsigned int bufferHeight = Dali::TizenPlatform::GetTextureDimension(scaledPreXformHeight);
+
+ bool result = false;
+
+ switch(transform)
+ {
+ case JpegTransform::NONE:
+ {
+ result = true;
+ break;
+ }
+ // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
+ case JpegTransform::ROTATE_180:
+ {
+ static auto rotate180Functions = TransformFunctionArray{
+ &Rotate180<1>,
+ &Rotate180<3>,
+ &Rotate180<4>,
+ };
+ result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+ break;
+ }
+ case JpegTransform::ROTATE_270:
+ {
+ static auto rotate270Functions = TransformFunctionArray{
+ &Rotate270<1>,
+ &Rotate270<3>,
+ &Rotate270<4>,
+ };
+ result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+ break;
+ }
+ case JpegTransform::ROTATE_90:
+ {
+ static auto rotate90Functions = TransformFunctionArray{
+ &Rotate90<1>,
+ &Rotate90<3>,
+ &Rotate90<4>,
+ };
+ result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+ break;
+ }
+ case JpegTransform::FLIP_VERTICAL:
+ {
+ static auto flipVerticalFunctions = TransformFunctionArray{
+ &FlipVertical<1>,
+ &FlipVertical<3>,
+ &FlipVertical<4>,
+ };
+ result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+ break;
+ }
+ // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
+ case JpegTransform::FLIP_HORIZONTAL:
+ {
+ static auto flipHorizontalFunctions = TransformFunctionArray{
+ &FlipHorizontal<1>,
+ &FlipHorizontal<3>,
+ &FlipHorizontal<4>,
+ };
+ result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+ break;
+ }
+ case JpegTransform::TRANSPOSE:
+ {
+ static auto transposeFunctions = TransformFunctionArray{
+ &Transpose<1>,
+ &Transpose<3>,
+ &Transpose<4>,
+ };
+ result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+ break;
+ }
+ case JpegTransform::TRANSVERSE:
+ {
+ static auto transverseFunctions = TransformFunctionArray{
+ &Transverse<1>,
+ &Transverse<3>,
+ &Transverse<4>,
+ };
+ result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+ break;
+ }
+ default:
+ {
+ DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
+ break;
+ }
+ }
+ return result;
+}
+
+bool LoadJpegFile(const Dali::ImageLoader::Input& input, Vector<uint8_t>& jpegBuffer, unsigned int& jpegBufferSize)
+{
+ FILE* const fp = input.file;
+
+ if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END)))
+ {
+ DALI_LOG_ERROR("Error seeking to end of file\n");
+ return false;
+ }
+
+ long positionIndicator = ftell(fp);
+ jpegBufferSize = 0u;
+ if(positionIndicator > -1L)
+ {
+ jpegBufferSize = static_cast<unsigned int>(positionIndicator);
+ }
+
+ if(DALI_UNLIKELY(0u == jpegBufferSize))
+ {
+ DALI_LOG_ERROR("Jpeg buffer size error\n");
+ return false;
+ }
+
+ if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
+ {
+ DALI_LOG_ERROR("Error seeking to start of file\n");
+ return false;
+ }
+
+ try
+ {
+ jpegBuffer.ResizeUninitialized(jpegBufferSize);
+ }
+ catch(...)
+ {
+ DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
+ return false;
+ }
+ uint8_t* const jpegBufferPtr = jpegBuffer.Begin();
+
+ // Pull the compressed JPEG image bytes out of a file and into memory:
+ if(DALI_UNLIKELY(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize))
+ {
+ DALI_LOG_ERROR("Error on image file read.\n");
+ return false;
+ }
+
+ if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
+ {
+ DALI_LOG_ERROR("Error seeking to start of file\n");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * @brief Helper function to convert from Turbo Jpeg Pixel Format as TJPF_CMYK to RGB888 by naive method.
+ *
+ * @param[in] cmykBuffer buffer of cmyk.
+ * @param[in] rgbBuffer buffer of Pixel::RGB888
+ * @param[in] width width of image.
+ * @param[in] height height of image.
+ */
+void ConvertTjpfCMYKToRGB888(PixelArray __restrict__ cmykBuffer, PixelArray __restrict__ rgbBuffer, int32_t width, int32_t height)
+{
+ const int32_t pixelCount = width * height;
+ const uint8_t cmykBpp = 4u;
+ const uint8_t bpp = 3u;
+
+ const PixelArray cmykBufferEnd = cmykBuffer + pixelCount * cmykBpp;
+ // Convert every pixel
+ while(cmykBuffer != cmykBufferEnd)
+ {
+ const uint8_t channelK = *(cmykBuffer + 3u);
+ *(rgbBuffer + 0u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 0u), channelK);
+ *(rgbBuffer + 1u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 1u), channelK);
+ *(rgbBuffer + 2u) = Dali::Internal::Platform::MultiplyAndNormalizeColor(*(cmykBuffer + 2u), channelK);
+ cmykBuffer += cmykBpp;
+ rgbBuffer += bpp;
+ }
+}
} // namespace
namespace Dali
{
namespace TizenPlatform
{
+bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv);
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);
// On error exit from the JPEG lib, control will pass via JpegErrorHandler
// into this branch body for cleanup and error return:
- if(setjmp(jerr.jumpBuffer))
+ if(DALI_UNLIKELY(setjmp(jerr.jumpBuffer)))
{
DALI_LOG_ERROR("setjmp failed\n");
jpeg_destroy_decompress(&cinfo);
jpeg_stdio_src(&cinfo, fp);
// Check header to see if it is JPEG file
- if(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
+ if(DALI_UNLIKELY(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK))
{
DALI_LOG_ERROR("jpeg_read_header failed\n");
width = height = 0;
bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
{
- const int flags = 0;
- FILE* const fp = input.file;
+ std::vector<Dali::Devel::PixelBuffer> pixelBuffers;
- if(fseek(fp, 0, SEEK_END))
+ bool result = DecodeJpeg(input, pixelBuffers, false);
+ if(!result && pixelBuffers.empty())
{
- DALI_LOG_ERROR("Error seeking to end of file\n");
- return false;
+ bitmap.Reset();
}
-
- long positionIndicator = ftell(fp);
- unsigned int jpegBufferSize = 0u;
- if(positionIndicator > -1L)
+ else
{
- jpegBufferSize = static_cast<unsigned int>(positionIndicator);
- }
-
- if(0u == jpegBufferSize)
- {
- DALI_LOG_ERROR("Jpeg buffer size error\n");
- return false;
+ bitmap = pixelBuffers[0];
}
+ return result;
+}
- if(fseek(fp, 0, SEEK_SET))
- {
- DALI_LOG_ERROR("Error seeking to start of file\n");
- return false;
- }
+bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
+{
+ return DecodeJpeg(input, pixelBuffers, true);
+}
- Vector<unsigned char> jpegBuffer;
- try
- {
- 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.Begin();
+bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv)
+{
+ Vector<uint8_t> jpegBuffer;
+ unsigned int jpegBufferSize = 0u;
- // Pull the compressed JPEG image bytes out of a file and into memory:
- if(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize)
+ if(!LoadJpegFile(input, jpegBuffer, jpegBufferSize))
{
- DALI_LOG_ERROR("Error on image file read.\n");
+ DALI_LOG_ERROR("LoadJpegFile failed\n");
return false;
}
- if(fseek(fp, 0, SEEK_SET))
- {
- DALI_LOG_ERROR("Error seeking to start of file\n");
- }
-
auto jpeg = MakeJpegDecompressor();
-
- if(!jpeg)
+ if(DALI_UNLIKELY(!jpeg))
{
DALI_LOG_ERROR("%s\n", tjGetErrorStr());
return false;
}
- auto transform = JpegTransform::NONE;
+ uint8_t* const jpegBufferPtr = jpegBuffer.Begin();
+ auto transform = JpegTransform::NONE;
// extract exif data
auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
transform = ConvertExifOrientation(exifData.get());
}
- std::unique_ptr<Property::Map> 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)
- {
- auto&& tag = content->entries[i];
- const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
- if(shortName)
- {
- AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
- }
- }
- }
-
// Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
int chrominanceSubsampling = -1;
int preXformImageWidth = 0, preXformImageHeight = 0;
-
- // 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)
+ if(DALI_UNLIKELY(preXformImageWidth == 0 || preXformImageHeight == 0))
{
DALI_LOG_ERROR("Invalid Image!\n");
return false;
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)
+ bool result = false;
+
+ // Check decoding format
+ if(decodeToYuv && IsSubsamplingFormatEnabled(chrominanceSubsampling) && transform == JpegTransform::NONE)
{
- 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:
+ uint8_t* planes[3];
+
+ // Allocate buffers for each plane and decompress the jpeg buffer into the buffers
+ for(int i = 0; i < 3; i++)
{
- 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);
+ auto planeSize = tjPlaneSizeYUV(i, scaledPostXformWidth, 0, scaledPostXformHeight, chrominanceSubsampling);
- // set metadata
- GetImplementation(bitmap).SetMetadata(std::move(exifMap));
+ uint8_t* buffer = static_cast<uint8_t*>(malloc(planeSize));
+ if(!buffer)
+ {
+ DALI_LOG_ERROR("Buffer allocation is failed [%d]\n", planeSize);
+ pixelBuffers.clear();
+ return false;
+ }
- auto bitmapPixelBuffer = bitmap.GetBuffer();
+ int width, height, planeWidth;
+ Pixel::Format pixelFormat = Pixel::RGB888;
- if(tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags) == -1)
- {
- std::string errorString = tjGetErrorStr();
+ if(i == 0)
+ {
+ // luminance plane
+ width = scaledPostXformWidth;
+ height = scaledPostXformHeight;
+ planeWidth = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
+ pixelFormat = Pixel::L8;
+ }
+ else
+ {
+ // chrominance plane
+ width = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
+ height = tjPlaneHeight(i, scaledPostXformHeight, chrominanceSubsampling);
+ planeWidth = width;
+ pixelFormat = (i == 1 ? Pixel::CHROMINANCE_U : Pixel::CHROMINANCE_V);
+ }
- if(IsJpegErrorFatal(errorString))
- {
- DALI_LOG_ERROR("%s\n", errorString.c_str());
- return false;
+ Internal::Adaptor::PixelBufferPtr internal = Internal::Adaptor::PixelBuffer::New(buffer, planeSize, width, height, planeWidth, pixelFormat);
+ Dali::Devel::PixelBuffer bitmap = Devel::PixelBuffer(internal.Get());
+ planes[i] = buffer;
+ pixelBuffers.push_back(bitmap);
}
- else
+
+ const int flags = 0;
+
+ int decodeResult = tjDecompressToYUVPlanes(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t**>(&planes), scaledPostXformWidth, nullptr, scaledPostXformHeight, flags);
+ if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
{
- DALI_LOG_WARNING("%s\n", errorString.c_str());
+ pixelBuffers.clear();
+ return false;
}
+
+ result = true;
}
+ else
+ {
+ // Colorspace conversion options
+ TJPF pixelLibJpegType = TJPF_RGB;
+ Pixel::Format pixelFormat = Pixel::RGB888;
- const unsigned int bufferWidth = GetTextureDimension(scaledPreXformWidth);
- const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight);
+ GetJpegPixelFormat(jpegColorspace, pixelLibJpegType, pixelFormat);
- bool result = false;
- switch(transform)
- {
- case JpegTransform::NONE:
- {
- result = true;
- break;
- }
- // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
- case JpegTransform::ROTATE_180:
- {
- static auto rotate180Functions = TransformFunctionArray{
- &Rotate180<1>,
- &Rotate180<3>,
- &Rotate180<4>,
- };
- result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
- break;
- }
- case JpegTransform::ROTATE_270:
- {
- static auto rotate270Functions = TransformFunctionArray{
- &Rotate270<1>,
- &Rotate270<3>,
- &Rotate270<4>,
- };
- result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
- break;
- }
- case JpegTransform::ROTATE_90:
- {
- static auto rotate90Functions = TransformFunctionArray{
- &Rotate90<1>,
- &Rotate90<3>,
- &Rotate90<4>,
- };
- result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
- break;
- }
- case JpegTransform::FLIP_VERTICAL:
- {
- static auto flipVerticalFunctions = TransformFunctionArray{
- &FlipVertical<1>,
- &FlipVertical<3>,
- &FlipVertical<4>,
- };
- result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
- break;
- }
- // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
- case JpegTransform::FLIP_HORIZONTAL:
- {
- static auto flipHorizontalFunctions = TransformFunctionArray{
- &FlipHorizontal<1>,
- &FlipHorizontal<3>,
- &FlipHorizontal<4>,
- };
- result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
- break;
- }
- case JpegTransform::TRANSPOSE:
+ // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
+ Dali::Devel::PixelBuffer bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
+
+ // Set metadata
+ if(DALI_LIKELY(exifData))
{
- static auto transposeFunctions = TransformFunctionArray{
- &Transpose<1>,
- &Transpose<3>,
- &Transpose<4>,
- };
- result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
- break;
+ std::unique_ptr<Property::Map> exifMap = std::make_unique<Property::Map>();
+
+ for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
+ {
+ auto content = exifData->ifd[k];
+ for(auto i = 0u; i < content->count; ++i)
+ {
+ auto&& tag = content->entries[i];
+ const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
+ if(shortName)
+ {
+ AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
+ }
+ }
+ }
+
+ GetImplementation(bitmap).SetMetadata(std::move(exifMap));
}
- case JpegTransform::TRANSVERSE:
+
+ auto bitmapPixelBuffer = bitmap.GetBuffer();
+ const int flags = 0;
+
+ int decodeResult = -1;
+ if(pixelLibJpegType == TJPF_CMYK)
{
- static auto transverseFunctions = TransformFunctionArray{
- &Transverse<1>,
- &Transverse<3>,
- &Transverse<4>,
- };
- result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
- break;
+ // Currently we support only for 4 bytes per each CMYK pixel.
+ const uint8_t cmykBytesPerPixel = 4u;
+
+ uint8_t* cmykBuffer = static_cast<uint8_t*>(malloc(sizeof(uint8_t) * scaledPostXformWidth * scaledPostXformHeight * cmykBytesPerPixel));
+
+ decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(cmykBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
+ if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
+ {
+ free(cmykBuffer);
+ return false;
+ }
+ ConvertTjpfCMYKToRGB888(cmykBuffer, bitmapPixelBuffer, scaledPostXformWidth, scaledPostXformHeight);
+
+ free(cmykBuffer);
}
- default:
+ else
{
- DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
- break;
+ decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
+ if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
+ {
+ return false;
+ }
}
+ pixelBuffers.push_back(bitmap);
+
+ // Transform bitmap
+ result = TransformBitmap(scaledPreXformWidth, scaledPreXformHeight, transform, bitmapPixelBuffer, pixelFormat);
}
return result;
}
-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)
+bool EncodeToJpeg(const uint8_t* const pixelBuffer, Vector<uint8_t>& encodedPixels, const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality)
{
- if(!pixelBuffer)
+ if(DALI_UNLIKELY(!pixelBuffer))
{
DALI_LOG_ERROR("Null input buffer\n");
return false;
// Initialise a JPEG codec:
{
auto jpeg = MakeJpegCompressor();
- if(!jpeg)
+ if(DALI_UNLIKELY(!jpeg))
{
DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
return false;
unsigned long dstBufferSize = 0;
const int flags = 0;
- if(tjCompress2(jpeg.get(),
- const_cast<unsigned char*>(pixelBuffer),
- width,
- 0,
- height,
- jpegPixelFormat,
- SetPointer(dstBuffer),
- &dstBufferSize,
- TJSAMP_444,
- quality,
- flags))
+ if(DALI_UNLIKELY(tjCompress2(jpeg.get(),
+ const_cast<uint8_t*>(pixelBuffer),
+ width,
+ 0,
+ height,
+ jpegPixelFormat,
+ SetPointer(dstBuffer),
+ &dstBufferSize,
+ TJSAMP_444,
+ quality,
+ flags)))
{
DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
return false;
}
- encodedPixels.Resize(dstBufferSize);
+ encodedPixels.ResizeUninitialized(dstBufferSize);
memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
}
return true;
int numFactors = 0;
tjscalingfactor* factors = tjGetScalingFactors(&numFactors);
- if(factors == NULL)
+ if(DALI_UNLIKELY(factors == NULL))
{
DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
success = false;
ExifHandle LoadExifData(FILE* fp)
{
- auto exifData = MakeNullExifData();
- unsigned char dataBuffer[1024];
+ auto exifData = MakeNullExifData();
+ uint8_t dataBuffer[1024];
- if(fseek(fp, 0, SEEK_SET))
+ if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
{
DALI_LOG_ERROR("Error seeking to start of file\n");
}
unsigned int headerHeight;
success = LoadJpegHeader(fp, headerWidth, headerHeight);
- if(success)
+ if(DALI_LIKELY(success))
{
auto transform = JpegTransform::NONE;
int postXformImageHeight = headerHeight;
success = TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
- if(success)
+ if(DALI_LIKELY(success))
{
headerWidth = postXformImageWidth;
headerHeight = postXformImageHeight;