From 7a6b672c7be2bf382fb82034511c10f7608242e7 Mon Sep 17 00:00:00 2001 From: Heeyong Song Date: Thu, 3 Feb 2022 16:10:18 +0900 Subject: [PATCH] [Tizen] Support YUV decoding for JPEG Change-Id: Ie7fddb22e70159821c27fb2ca1ccc2db19fe1ec4 --- .../adaptor-framework/image-loader-input.h | 16 +- dali/devel-api/adaptor-framework/image-loading.cpp | 16 +- dali/devel-api/adaptor-framework/image-loading.h | 22 +- dali/internal/imaging/common/image-loader.cpp | 87 ++++- dali/internal/imaging/common/image-loader.h | 12 +- dali/internal/imaging/common/loader-jpeg-turbo.cpp | 350 ++++++++++++++++++++- dali/internal/imaging/common/loader-jpeg.h | 11 +- 7 files changed, 488 insertions(+), 26 deletions(-) diff --git a/dali/devel-api/adaptor-framework/image-loader-input.h b/dali/devel-api/adaptor-framework/image-loader-input.h index 830b33c..80d7b61 100644 --- a/dali/devel-api/adaptor-framework/image-loader-input.h +++ b/dali/devel-api/adaptor-framework/image-loader-input.h @@ -2,7 +2,7 @@ #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. @@ -63,6 +63,7 @@ struct Input }; using LoadBitmapFunction = bool (*)(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& pixelData); +using LoadPlanesFunction = bool (*)(const Dali::ImageLoader::Input& input, std::vector& pixelBuffers); using LoadBitmapHeaderFunction = bool (*)(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height); /** @@ -70,12 +71,13 @@ using LoadBitmapHeaderFunction = bool (*)(const Dali::ImageLoader::Input& input, */ 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 diff --git a/dali/devel-api/adaptor-framework/image-loading.cpp b/dali/devel-api/adaptor-framework/image-loading.cpp index cfaaa12..207b79a 100644 --- a/dali/devel-api/adaptor-framework/image-loading.cpp +++ b/dali/devel-api/adaptor-framework/image-loading.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -51,6 +51,18 @@ Devel::PixelBuffer LoadImageFromFile(const std::string& url, ImageDimensions siz return Dali::Devel::PixelBuffer(); } +void LoadImagePlanesFromFile(const std::string& url, std::vector& 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& buffer, ImageDimensions size, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, bool orientationCorrection) { if(buffer.Empty()) @@ -66,7 +78,7 @@ Devel::PixelBuffer LoadImageFromBuffer(const Dali::Vector& buffer, Imag { 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; diff --git a/dali/devel-api/adaptor-framework/image-loading.h b/dali/devel-api/adaptor-framework/image-loading.h index c1f7ad8..61be92d 100644 --- a/dali/devel-api/adaptor-framework/image-loading.h +++ b/dali/devel-api/adaptor-framework/image-loading.h @@ -2,7 +2,7 @@ #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. @@ -47,6 +47,26 @@ DALI_ADAPTOR_API Devel::PixelBuffer LoadImageFromFile( 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. + * + * @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& 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. diff --git a/dali/internal/imaging/common/image-loader.cpp b/dali/internal/imaging/common/image-loader.cpp index 8c7139d..b87bddb 100644 --- a/dali/internal/imaging/common/image-loader.cpp +++ b/dali/internal/imaging/common/image-loader.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -80,15 +80,15 @@ enum FileFormats // 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 @@ -151,6 +151,7 @@ FileFormats GetFormatHint(const std::string& filename) 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) @@ -238,9 +239,10 @@ bool GetBitmapLoaderFunctions(FILE* fp, // 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. @@ -265,6 +267,7 @@ bool ConvertStreamToBitmap(const BitmapResourceType& resource, std::string path, if(fp != NULL) { Dali::ImageLoader::LoadBitmapFunction function; + Dali::ImageLoader::LoadPlanesFunction planeLoader; Dali::ImageLoader::LoadBitmapHeaderFunction header; Bitmap::Profile profile; @@ -272,6 +275,7 @@ bool ConvertStreamToBitmap(const BitmapResourceType& resource, std::string path, if(GetBitmapLoaderFunctions(fp, GetFormatHint(path), function, + planeLoader, header, profile, path)) @@ -299,6 +303,59 @@ bool ConvertStreamToBitmap(const BitmapResourceType& resource, std::string path, return result; } +bool ConvertStreamToPlanes(const Integration::BitmapResourceType& resource, std::string path, FILE* const fp, std::vector& 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); + + // Run the image type decoder: + if(planeLoader) + { + result = planeLoader(input, pixelBuffers); + } + else + { + Dali::Devel::PixelBuffer pixelBuffer; + result = loader(input, pixelBuffer); + if(!result) + { + DALI_LOG_ERROR("Unable to convert %s\n", path.c_str()); + } + + pixelBuffer = Internal::Platform::ApplyAttributesToBitmap(pixelBuffer, resource.size, resource.scalingMode, resource.samplingMode); + + pixelBuffers.push_back(pixelBuffer); + } + } + 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; @@ -349,12 +406,14 @@ ImageDimensions GetClosestImageSize(const std::string& filename, 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)) @@ -398,12 +457,14 @@ ImageDimensions GetClosestImageSize(Integration::ResourcePointer resourceBuffer, 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, "")) diff --git a/dali/internal/imaging/common/image-loader.h b/dali/internal/imaging/common/image-loader.h index f63b186..8af537d 100644 --- a/dali/internal/imaging/common/image-loader.h +++ b/dali/internal/imaging/common/image-loader.h @@ -2,7 +2,7 @@ #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. @@ -47,6 +47,16 @@ namespace ImageLoader bool ConvertStreamToBitmap(const Integration::BitmapResourceType& resource, std::string path, FILE* const fp, Dali::Devel::PixelBuffer& pixelBuffer); /** + * 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] pixelBuffers Pointer to write buffer to + * @return true on success, false on failure + */ +bool ConvertStreamToPlanes(const Integration::BitmapResourceType& resource, std::string path, FILE* const fp, std::vector& pixelBuffers); + +/** * Convert a bitmap and write to a file stream. * @param[in] path The path to the resource. * @param[in] fp File Pointer. Closed on exit. diff --git a/dali/internal/imaging/common/loader-jpeg-turbo.cpp b/dali/internal/imaging/common/loader-jpeg-turbo.cpp index 2068ef3..7d529d9 100644 --- a/dali/internal/imaging/common/loader-jpeg-turbo.cpp +++ b/dali/internal/imaging/common/loader-jpeg-turbo.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include // INTERNAL HEADERS +#include #include #include #include @@ -50,6 +52,8 @@ const unsigned int DECODED_L8 = 1; const unsigned int DECODED_RGB888 = 3; const unsigned int DECODED_RGBA8888 = 4; +constexpr auto DECODE_JPEG_TO_YUV_ENV = "DALI_DECODE_JPEG_TO_YUV_ENV"; + /** Transformations that can be applied to decoded pixels to respect exif orientation * codes in image headers */ enum class JpegTransform @@ -812,6 +816,350 @@ bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::Pixe return result; } +bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector& pixelBuffers) +{ + const int flags = 0; + FILE* const fp = input.file; + + if(fseek(fp, 0, SEEK_END)) + { + DALI_LOG_ERROR("Error seeking to end of file\n"); + return false; + } + + long positionIndicator = ftell(fp); + unsigned int jpegBufferSize = 0u; + if(positionIndicator > -1L) + { + jpegBufferSize = static_cast(positionIndicator); + } + + if(0u == jpegBufferSize) + { + DALI_LOG_ERROR("Jpeg buffer size error\n"); + return false; + } + + if(fseek(fp, 0, SEEK_SET)) + { + DALI_LOG_ERROR("Error seeking to start of file\n"); + return false; + } + + Vector 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(); + + // Pull the compressed JPEG image bytes out of a file and into memory: + if(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize) + { + DALI_LOG_ERROR("Error on image file read.\n"); + return false; + } + + if(fseek(fp, 0, SEEK_SET)) + { + DALI_LOG_ERROR("Error seeking to start of file\n"); + } + + auto jpeg = MakeJpegDecompressor(); + + if(!jpeg) + { + DALI_LOG_ERROR("%s\n", tjGetErrorStr()); + return false; + } + + auto transform = JpegTransform::NONE; + + // extract exif data + auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize); + + if(exifData && input.reorientationRequested) + { + transform = ConvertExifOrientation(exifData.get()); + } + + std::unique_ptr exifMap; + exifMap.reset(new Property::Map()); + + for(auto k = 0u; k < EXIF_IFD_COUNT; ++k) + { + auto content = exifData->ifd[k]; + for(auto i = 0u; i < content->count; ++i) + { + auto&& tag = content->entries[i]; + const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast(k)); + if(shortName) + { + AddExifFieldPropertyMap(*exifMap, *tag, static_cast(k)); + } + } + } + + // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array: + int chrominanceSubsampling = -1; + int preXformImageWidth = 0, preXformImageHeight = 0; + + 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. + } + + if(preXformImageWidth == 0 || preXformImageHeight == 0) + { + DALI_LOG_ERROR("Invalid Image!\n"); + return false; + } + + 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 + // rotating that too, and the final width and height need to be swapped: + int postXformImageWidth = preXformImageWidth; + int postXformImageHeight = preXformImageHeight; + + int scaledPreXformWidth = preXformImageWidth; + int scaledPreXformHeight = preXformImageHeight; + int scaledPostXformWidth = postXformImageWidth; + int scaledPostXformHeight = postXformImageHeight; + + TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight); + + auto decodeToYuvString = EnvironmentVariable::GetEnvironmentVariable(DECODE_JPEG_TO_YUV_ENV); + bool decodeToYuv = decodeToYuvString ? std::atoi(decodeToYuvString) : false; + int decodeResult = -1; + bool result = false; + + // Now we support YUV420 only + if(decodeToYuv && chrominanceSubsampling == TJSAMP_420 && transform == JpegTransform::NONE) + { + unsigned char* planes[3]; + + // Allocate buffers for each plane and decompress the jpeg buffer into the buffers + for(int i = 0; i < 3; i++) + { + auto planeSize = tjPlaneSizeYUV(i, scaledPostXformWidth, 0, scaledPostXformHeight, chrominanceSubsampling); + + unsigned char* buffer = static_cast(malloc(planeSize)); + if(!buffer) + { + DALI_LOG_ERROR("Buffer allocation is failed [%d]\n", planeSize); + pixelBuffers.clear(); + return false; + } + + int width, height, planeWidth; + + if(i == 0) + { + // luminance + width = scaledPostXformWidth; + height = scaledPostXformHeight; + planeWidth = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling); + } + else + { + width = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling); + height = tjPlaneHeight(i, scaledPostXformHeight, chrominanceSubsampling); + planeWidth = width; + } + + Internal::Adaptor::PixelBufferPtr internal = Internal::Adaptor::PixelBuffer::New(buffer, planeSize, width, height, planeWidth, Pixel::L8); + Dali::Devel::PixelBuffer bitmap = Devel::PixelBuffer(internal.Get()); + + planes[i] = buffer; + + pixelBuffers.push_back(bitmap); + } + + decodeResult = tjDecompressToYUVPlanes(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast(&planes), scaledPostXformWidth, nullptr, scaledPostXformHeight, flags); + if(decodeResult == -1) + { + std::string errorString = tjGetErrorStr(); + + if(IsJpegErrorFatal(errorString)) + { + DALI_LOG_ERROR("%s\n", errorString.c_str()); + pixelBuffers.clear(); + return false; + } + else + { + DALI_LOG_WARNING("%s\n", errorString.c_str()); + } + } + + result = true; + } + else + { + // Colorspace conversion options + TJPF pixelLibJpegType = TJPF_RGB; + Pixel::Format 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; + } + } + + // 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 + GetImplementation(bitmap).SetMetadata(std::move(exifMap)); + + auto bitmapPixelBuffer = bitmap.GetBuffer(); + + decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags); + if(decodeResult == -1) + { + std::string errorString = tjGetErrorStr(); + + if(IsJpegErrorFatal(errorString)) + { + DALI_LOG_ERROR("%s\n", errorString.c_str()); + return false; + } + else + { + DALI_LOG_WARNING("%s\n", errorString.c_str()); + } + } + pixelBuffers.push_back(bitmap); + + const unsigned int bufferWidth = GetTextureDimension(scaledPreXformWidth); + const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight); + + 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 EncodeToJpeg(const unsigned char* const pixelBuffer, Vector& encodedPixels, const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality) { if(!pixelBuffer) diff --git a/dali/internal/imaging/common/loader-jpeg.h b/dali/internal/imaging/common/loader-jpeg.h index 45c99e4..93cc310 100644 --- a/dali/internal/imaging/common/loader-jpeg.h +++ b/dali/internal/imaging/common/loader-jpeg.h @@ -2,7 +2,7 @@ #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. @@ -51,6 +51,15 @@ const unsigned char MAGIC_BYTE_2 = 0xD8; 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 + */ +bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector& 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 -- 2.7.4