/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
// resolution: 2000*2560, pixel format: RGB888
const char* IMAGE_LARGE_EXIF3_RGB = TEST_RESOURCE_DIR "/f-large-exif-3.jpg";
+// resolution: 2048*2048, pixel format: RGB888, YUV420
+const char* IMAGE_LARGE_2048_YUV_420 = TEST_RESOURCE_DIR "/lake_front.jpg";
+
// resolution: 55*64, pixel format: RGB888
const char* IMAGE_WIDTH_ODD_EXIF1_RGB = TEST_RESOURCE_DIR "/f-odd-exif-1.jpg";
// resolution: 55*64, pixel format: RGB888
Dali::Vector<uint8_t> FileToMemory(const char* filename)
{
Dali::Vector<uint8_t> buffer;
- FILE *fp;
+ FILE* fp;
fp = fopen(filename, "rb");
if(fp != NULL)
{
END_TEST;
}
+
+int UtcDaliLoadImagePlanesFromFileP(void)
+{
+ std::vector<Devel::PixelBuffer> pixelBuffers;
+
+ Dali::LoadImagePlanesFromFile(IMAGE_LARGE_2048_YUV_420, pixelBuffers);
+ DALI_TEST_EQUALS(pixelBuffers.size(), 3, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetWidth(), 2048u, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetHeight(), 2048u, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetPixelFormat(), Pixel::L8, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[1].GetPixelFormat(), Pixel::CHROMINANCE_U, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[2].GetPixelFormat(), Pixel::CHROMINANCE_V, TEST_LOCATION);
+
+ pixelBuffers.clear();
+
+ // Test not supported image format: png
+ Dali::LoadImagePlanesFromFile(IMAGE_34_RGBA, pixelBuffers);
+ DALI_TEST_EQUALS(pixelBuffers.size(), 1, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetWidth(), 34u, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetHeight(), 34u, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetPixelFormat(), Pixel::RGBA8888, TEST_LOCATION);
+
+ pixelBuffers.clear();
+
+ // Test notsupported chrominace subsampling case
+ Dali::LoadImagePlanesFromFile(IMAGE_128_RGB, pixelBuffers);
+ DALI_TEST_EQUALS(pixelBuffers.size(), 1, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetWidth(), 128u, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetHeight(), 128u, TEST_LOCATION);
+ DALI_TEST_EQUALS(pixelBuffers[0].GetPixelFormat(), Pixel::RGB888, TEST_LOCATION);
+
+ END_TEST;
+}
+
+int UtcDaliLoadImagePlanesFromFileN(void)
+{
+ std::vector<Devel::PixelBuffer> pixelBuffers;
+
+ Dali::LoadImagePlanesFromFile(IMAGENONEXIST, pixelBuffers);
+ DALI_TEST_CHECK(pixelBuffers.empty());
+
+ END_TEST;
+}
#define DALI_TIZEN_PLATFORM_IMAGE_LOADER_INPUT_H
/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
};
using LoadBitmapFunction = bool (*)(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& pixelData);
+using LoadPlanesFunction = bool (*)(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers);
using LoadBitmapHeaderFunction = bool (*)(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height);
/**
*/
struct BitmapLoader
{
- unsigned char magicByte1; ///< The first byte in the file should be this
- unsigned char magicByte2; ///< The second byte in the file should be this
- LoadBitmapFunction loader; ///< The function which decodes the file
- LoadBitmapHeaderFunction header; ///< The function which decodes the header of the file
- Dali::Integration::Bitmap::Profile profile; ///< The kind of bitmap to be created
- /// (addressable packed pixels or an opaque compressed blob).
+ unsigned char magicByte1; ///< The first byte in the file should be this
+ unsigned char magicByte2; ///< The second byte in the file should be this
+ LoadBitmapFunction loader; ///< The function which decodes the file
+ LoadPlanesFunction planeLoader; ///< The function which decodes the file to each plane
+ LoadBitmapHeaderFunction header; ///< The function which decodes the header of the file
+ Dali::Integration::Bitmap::Profile profile; ///< The kind of bitmap to be created
+ /// (addressable packed pixels or an opaque compressed blob).
};
} // namespace ImageLoader
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
return Dali::Devel::PixelBuffer();
}
+void LoadImagePlanesFromFile(const std::string& url, std::vector<Devel::PixelBuffer>& buffers, ImageDimensions size, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, bool orientationCorrection)
+{
+ Integration::BitmapResourceType resourceType(size, fittingMode, samplingMode, orientationCorrection);
+
+ Internal::Platform::FileReader fileReader(url);
+ FILE* const fp = fileReader.GetFile();
+ if(fp != NULL)
+ {
+ TizenPlatform::ImageLoader::ConvertStreamToPlanes(resourceType, url, fp, buffers);
+ }
+}
+
Devel::PixelBuffer LoadImageFromBuffer(const Dali::Vector<uint8_t>& buffer, ImageDimensions size, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, bool orientationCorrection)
{
if(buffer.Empty())
{
Dali::Devel::PixelBuffer bitmap;
// Make path as empty string. Path information just for file format hint.
- bool success = TizenPlatform::ImageLoader::ConvertStreamToBitmap(resourceType, std::string(""), fp, bitmap);
+ bool success = TizenPlatform::ImageLoader::ConvertStreamToBitmap(resourceType, std::string(""), fp, bitmap);
if(success && bitmap)
{
return bitmap;
#define DALI_IMAGE_LOADING_H
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
bool orientationCorrection = true);
/**
+ * @brief Load an image and save each plane to a separate buffer synchronously from local file.
+ *
+ * @note This method is thread safe, i.e. can be called from any thread.
+ * If the image file doesn't support to load planes, this method returns a bitmap image instead.
+ *
+ * @param [in] url The URL of the image file to load.
+ * @param [out] buffers The loaded PixelBuffer object list or an empty list in case loading failed.
+ * @param [in] size The width and height to fit the loaded image to, 0.0 means whole image
+ * @param [in] fittingMode The method used to fit the shape of the image before loading to the shape defined by the size parameter.
+ * @param [in] samplingMode The filtering method used when sampling pixels from the input image while fitting it to desired size.
+ * @param [in] orientationCorrection Reorient the image to respect any orientation metadata in its header.
+ */
+DALI_ADAPTOR_API void LoadImagePlanesFromFile(
+ const std::string& url,
+ std::vector<Devel::PixelBuffer>& buffers,
+ ImageDimensions size = ImageDimensions(0, 0),
+ FittingMode::Type fittingMode = FittingMode::DEFAULT,
+ SamplingMode::Type samplingMode = SamplingMode::BOX_THEN_LINEAR,
+ bool orientationCorrection = true);
+
+/**
* @brief Load an image synchronously from encoded buffer.
*
* @note This method is thread safe, i.e. can be called from any thread.
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
// clang-format off
const Dali::ImageLoader::BitmapLoader BITMAP_LOADER_LOOKUP_TABLE[FORMAT_TOTAL_COUNT] =
{
- {Png::MAGIC_BYTE_1, Png::MAGIC_BYTE_2, LoadBitmapFromPng, LoadPngHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
- {Jpeg::MAGIC_BYTE_1, Jpeg::MAGIC_BYTE_2, LoadBitmapFromJpeg, LoadJpegHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
- {Bmp::MAGIC_BYTE_1, Bmp::MAGIC_BYTE_2, LoadBitmapFromBmp, LoadBmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
- {Gif::MAGIC_BYTE_1, Gif::MAGIC_BYTE_2, LoadBitmapFromGif, LoadGifHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
- {Webp::MAGIC_BYTE_1, Webp::MAGIC_BYTE_2, LoadBitmapFromWebp, LoadWebpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
- {Ktx::MAGIC_BYTE_1, Ktx::MAGIC_BYTE_2, LoadBitmapFromKtx, LoadKtxHeader, Bitmap::BITMAP_COMPRESSED },
- {Astc::MAGIC_BYTE_1, Astc::MAGIC_BYTE_2, LoadBitmapFromAstc, LoadAstcHeader, Bitmap::BITMAP_COMPRESSED },
- {Ico::MAGIC_BYTE_1, Ico::MAGIC_BYTE_2, LoadBitmapFromIco, LoadIcoHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
- {0x0, 0x0, LoadBitmapFromWbmp, LoadWbmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
+ {Png::MAGIC_BYTE_1, Png::MAGIC_BYTE_2, LoadBitmapFromPng, nullptr, LoadPngHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
+ {Jpeg::MAGIC_BYTE_1, Jpeg::MAGIC_BYTE_2, LoadBitmapFromJpeg, LoadPlanesFromJpeg, LoadJpegHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
+ {Bmp::MAGIC_BYTE_1, Bmp::MAGIC_BYTE_2, LoadBitmapFromBmp, nullptr, LoadBmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
+ {Gif::MAGIC_BYTE_1, Gif::MAGIC_BYTE_2, LoadBitmapFromGif, nullptr, LoadGifHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
+ {Webp::MAGIC_BYTE_1, Webp::MAGIC_BYTE_2, LoadBitmapFromWebp, nullptr, LoadWebpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
+ {Ktx::MAGIC_BYTE_1, Ktx::MAGIC_BYTE_2, LoadBitmapFromKtx, nullptr, LoadKtxHeader, Bitmap::BITMAP_COMPRESSED },
+ {Astc::MAGIC_BYTE_1, Astc::MAGIC_BYTE_2, LoadBitmapFromAstc, nullptr, LoadAstcHeader, Bitmap::BITMAP_COMPRESSED },
+ {Ico::MAGIC_BYTE_1, Ico::MAGIC_BYTE_2, LoadBitmapFromIco, nullptr, LoadIcoHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
+ {0x0, 0x0, LoadBitmapFromWbmp, nullptr, LoadWbmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS},
};
// clang-format on
bool GetBitmapLoaderFunctions(FILE* fp,
FileFormats format,
Dali::ImageLoader::LoadBitmapFunction& loader,
+ Dali::ImageLoader::LoadPlanesFunction& planeLoader,
Dali::ImageLoader::LoadBitmapHeaderFunction& header,
Bitmap::Profile& profile,
const std::string& filename)
// if a loader was found set the outputs
if(loaderFound)
{
- loader = lookupPtr->loader;
- header = lookupPtr->header;
- profile = lookupPtr->profile;
+ loader = lookupPtr->loader;
+ planeLoader = lookupPtr->planeLoader;
+ header = lookupPtr->header;
+ profile = lookupPtr->profile;
}
// Reset to the start of the file.
namespace ImageLoader
{
-bool ConvertStreamToBitmap(const BitmapResourceType& resource, std::string path, FILE* const fp, Dali::Devel::PixelBuffer& pixelBuffer)
+bool ConvertStreamToBitmap(const BitmapResourceType& resource, const std::string& path, FILE* const fp, Dali::Devel::PixelBuffer& pixelBuffer)
{
DALI_LOG_TRACE_METHOD(gLogFilter);
if(fp != NULL)
{
Dali::ImageLoader::LoadBitmapFunction function;
+ Dali::ImageLoader::LoadPlanesFunction planeLoader;
Dali::ImageLoader::LoadBitmapHeaderFunction header;
Bitmap::Profile profile;
if(GetBitmapLoaderFunctions(fp,
GetFormatHint(path),
function,
+ planeLoader,
header,
profile,
path))
return result;
}
+bool ConvertStreamToPlanes(const Integration::BitmapResourceType& resource, const std::string& path, FILE* const fp, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
+{
+ DALI_LOG_TRACE_METHOD(gLogFilter);
+
+ bool result = false;
+
+ if(fp != NULL)
+ {
+ Dali::ImageLoader::LoadBitmapFunction loader;
+ Dali::ImageLoader::LoadPlanesFunction planeLoader;
+ Dali::ImageLoader::LoadBitmapHeaderFunction header;
+
+ Bitmap::Profile profile;
+
+ if(GetBitmapLoaderFunctions(fp,
+ GetFormatHint(path),
+ loader,
+ planeLoader,
+ header,
+ profile,
+ path))
+ {
+ const Dali::ImageLoader::ScalingParameters scalingParameters(resource.size, resource.scalingMode, resource.samplingMode);
+ const Dali::ImageLoader::Input input(fp, scalingParameters, resource.orientationCorrection);
+
+ pixelBuffers.clear();
+
+ // Run the image type decoder:
+ if(planeLoader)
+ {
+ result = planeLoader(input, pixelBuffers);
+ if(!result)
+ {
+ DALI_LOG_ERROR("Unable to convert %s\n", path.c_str());
+ }
+ }
+ else
+ {
+ Dali::Devel::PixelBuffer pixelBuffer;
+ result = loader(input, pixelBuffer);
+ if(!result)
+ {
+ DALI_LOG_ERROR("Unable to convert %s\n", path.c_str());
+ return false;
+ }
+
+ pixelBuffer = Internal::Platform::ApplyAttributesToBitmap(pixelBuffer, resource.size, resource.scalingMode, resource.samplingMode);
+ if(pixelBuffer)
+ {
+ pixelBuffers.push_back(pixelBuffer);
+ }
+ else
+ {
+ DALI_LOG_ERROR("ApplyAttributesToBitmap is failed [%s]\n", path.c_str());
+ return false;
+ }
+ }
+ }
+ else
+ {
+ DALI_LOG_ERROR("Image Decoder for %s unavailable\n", path.c_str());
+ }
+ }
+
+ return result;
+}
+
ResourcePointer LoadImageSynchronously(const Integration::BitmapResourceType& resource, const std::string& path)
{
ResourcePointer result;
if(fp != NULL)
{
Dali::ImageLoader::LoadBitmapFunction loaderFunction;
+ Dali::ImageLoader::LoadPlanesFunction planeLoader;
Dali::ImageLoader::LoadBitmapHeaderFunction headerFunction;
Bitmap::Profile profile;
if(GetBitmapLoaderFunctions(fp,
GetFormatHint(filename),
loaderFunction,
+ planeLoader,
headerFunction,
profile,
filename))
if(fp != NULL)
{
Dali::ImageLoader::LoadBitmapFunction loaderFunction;
+ Dali::ImageLoader::LoadPlanesFunction planeLoader;
Dali::ImageLoader::LoadBitmapHeaderFunction headerFunction;
Bitmap::Profile profile;
if(GetBitmapLoaderFunctions(fp,
FORMAT_UNKNOWN,
loaderFunction,
+ planeLoader,
headerFunction,
profile,
""))
#define DALI_TIZEN_PLATFORM_IMAGE_LOADER_H
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
* @param[out] bitmap Pointer to write bitmap to
* @return true on success, false on failure
*/
-bool ConvertStreamToBitmap(const Integration::BitmapResourceType& resource, std::string path, FILE* const fp, Dali::Devel::PixelBuffer& pixelBuffer);
+bool ConvertStreamToBitmap(const Integration::BitmapResourceType& resource, const std::string& path, FILE* const fp, Dali::Devel::PixelBuffer& pixelBuffer);
/**
- * Convert a bitmap and write to a file stream.
+ * Convert a file stream into image planes.
+ * @param[in] resource The resource to convert.
* @param[in] path The path to the resource.
* @param[in] fp File Pointer. Closed on exit.
- * @param[out] pixelData Reference to PixelData object.
+ * @param[out] pixelBuffers Pointer to write buffer to
* @return true on success, false on failure
+ * @note If the image file doesn't support to load planes, this method returns one RGB bitmap image.
*/
-bool ConvertBitmapToStream(std::string path, FILE* const fp, Dali::Devel::PixelBuffer& pixelBuffer);
+bool ConvertStreamToPlanes(const Integration::BitmapResourceType& resource, const std::string& path, FILE* const fp, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers);
/**
* Loads an image synchronously
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)*>;
}
}
+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::RGBA8888;
+ 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;
+ }
+ unsigned char* 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;
+}
+
} // 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);
bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
{
- const int flags = 0;
- 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;
- }
+ std::vector<Dali::Devel::PixelBuffer> pixelBuffers;
- long positionIndicator = ftell(fp);
- unsigned int jpegBufferSize = 0u;
- if(positionIndicator > -1L)
+ bool result = DecodeJpeg(input, pixelBuffers, false);
+ if(!result && pixelBuffers.empty())
{
- jpegBufferSize = static_cast<unsigned int>(positionIndicator);
+ bitmap.Reset();
}
-
- if(DALI_UNLIKELY(0u == jpegBufferSize))
+ else
{
- 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;
+ bitmap = pixelBuffers[0];
}
+ return result;
+}
- Vector<unsigned char> jpegBuffer;
- 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;
- }
- unsigned char* const jpegBufferPtr = jpegBuffer.Begin();
+bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
+{
+ return DecodeJpeg(input, pixelBuffers, true);
+}
- // 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;
- }
+bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv)
+{
+ Vector<uint8_t> jpegBuffer;
+ unsigned int jpegBufferSize = 0u;
- if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
+ if(!LoadJpegFile(input, jpegBuffer, jpegBufferSize))
{
- DALI_LOG_ERROR("Error seeking to start of file\n");
+ DALI_LOG_ERROR("LoadJpegFile failed\n");
return false;
}
auto jpeg = MakeJpegDecompressor();
-
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());
-
- if(DALI_LIKELY(exifData))
- {
- 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(DALI_UNLIKELY(preXformImageWidth == 0 || preXformImageHeight == 0))
{
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;
+
+ // Now we support YUV420 only
+ if(decodeToYuv && chrominanceSubsampling == TJSAMP_420 && 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:
+ unsigned char* 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));
+ unsigned char* buffer = static_cast<unsigned char*>(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(DALI_UNLIKELY(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<unsigned char**>(&planes), scaledPostXformWidth, nullptr, scaledPostXformHeight, flags);
+ if(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:
- {
- static auto transposeFunctions = TransformFunctionArray{
- &Transpose<1>,
- &Transpose<3>,
- &Transpose<4>,
- };
- result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
- break;
- }
- case JpegTransform::TRANSVERSE:
+ // 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 transverseFunctions = TransformFunctionArray{
- &Transverse<1>,
- &Transverse<3>,
- &Transverse<4>,
- };
- result = Transform(transverseFunctions, 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));
}
- default:
+
+ auto bitmapPixelBuffer = bitmap.GetBuffer();
+ const int flags = 0;
+
+ int decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
+ if(decodeResult == -1 && IsJpegDecodingFailed())
{
- DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
- break;
+ return false;
}
+ pixelBuffers.push_back(bitmap);
+
+ // Transform bitmap
+ result = TransformBitmap(scaledPreXformWidth, scaledPreXformHeight, transform, bitmapPixelBuffer, pixelFormat);
}
return result;
#define DALI_TIZEN_PLATFORM_LOADER_JPEG_H
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap);
/**
+ * Loads the image planes from an JPEG file. This function checks the header first
+ * and if it is not a JPEG file, then it returns straight away.
+ * @param[in] input Information about the input image (including file pointer)
+ * @param[out] pixelBuffers The buffer list where the each plane will be stored
+ * @return true if file decoded successfully, false otherwise
+ * @note If the image file doesn't support to load planes, this method returns one RGB bitmap image.
+ */
+bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers);
+
+/**
* Loads the header of a JPEG file and fills in the width and height appropriately.
* If the width and height are set on entry, it will set the width and height
* to the closest scaled size (exactly as will be loaded by LoadBitmapFromJpeg with the same
DALI_LOG_ERROR("Pixel formats for compressed images are not compatible with simple channels.\n");
break;
}
+
+ case Dali::Pixel::CHROMINANCE_U:
+ case Dali::Pixel::CHROMINANCE_V:
+ {
+ DALI_LOG_ERROR("Pixel formats for chrominance are not compatible with simple channels.\n");
+ break;
+ }
}
return false;