From d65d8d30f0110a617e02d502f64e9bbfac3b2586 Mon Sep 17 00:00:00 2001 From: "Eunki, Hong" Date: Wed, 16 Aug 2023 17:37:08 +0900 Subject: [PATCH] Support *.pkm format file decode Let we support .pkm format file decode, who use ETC1 and ETC2 pixel format. Change-Id: Ib1d7c0b3653f31fd6a4bb1fd378b7fe0cdcbb3a2 Signed-off-by: Eunki, Hong --- dali/internal/imaging/common/image-loader.cpp | 4 + dali/internal/imaging/common/loader-pkm.cpp | 263 +++++++++++++++++++++ dali/internal/imaging/common/loader-pkm.h | 63 +++++ dali/internal/imaging/common/pixel-buffer-impl.cpp | 9 +- dali/internal/imaging/file.list | 1 + 5 files changed, 335 insertions(+), 5 deletions(-) create mode 100644 dali/internal/imaging/common/loader-pkm.cpp create mode 100644 dali/internal/imaging/common/loader-pkm.h diff --git a/dali/internal/imaging/common/image-loader.cpp b/dali/internal/imaging/common/image-loader.cpp index 5f6623b..a88270a 100644 --- a/dali/internal/imaging/common/image-loader.cpp +++ b/dali/internal/imaging/common/image-loader.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,7 @@ enum FileFormats FORMAT_WEBP, FORMAT_KTX, FORMAT_ASTC, + FORMAT_PKM, FORMAT_ICO, FORMAT_MAGIC_BYTE_COUNT, @@ -90,6 +92,7 @@ const Dali::ImageLoader::BitmapLoader BITMAP_LOADER_LOOKUP_TABLE[FORMAT_TOTAL_CO {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 }, + {Pkm::MAGIC_BYTE_1, Pkm::MAGIC_BYTE_2, LoadBitmapFromPkm, nullptr, LoadPkmHeader, 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}, }; @@ -116,6 +119,7 @@ constexpr FormatExtension FORMAT_EXTENSIONS[] = {".webp", FORMAT_WEBP }, {".ktx", FORMAT_KTX }, {".astc", FORMAT_ASTC}, + {".pkm", FORMAT_PKM}, {".ico", FORMAT_ICO }, {".wbmp", FORMAT_WBMP}, {".svg", FORMAT_UNSUPPORTED}, // SVG diff --git a/dali/internal/imaging/common/loader-pkm.cpp b/dali/internal/imaging/common/loader-pkm.cpp new file mode 100644 index 0000000..71b1404 --- /dev/null +++ b/dali/internal/imaging/common/loader-pkm.cpp @@ -0,0 +1,263 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include + +// EXTERNAL INCLUDES +#include +#include +#include +#include ///< for memcmp + +// INTERNAL INCLUDES +#include ///< for Internal::Adaptor::PixelBuffer::New() + +namespace Dali +{ +namespace TizenPlatform +{ +namespace +{ +// Max width or height of an image. +const unsigned MAX_TEXTURE_DIMENSION = 4096; +// Max bytes of image data allowed. Not a precise number, just a sanity check. +const unsigned MAX_IMAGE_DATA_SIZE = MAX_TEXTURE_DIMENSION * MAX_TEXTURE_DIMENSION; + +const uint8_t PKM_10_VERSION_MAJOR = '1'; +const uint8_t PKM_10_VERSION_MINOR = '0'; + +const uint8_t PKM_20_VERSION_MAJOR = '2'; +const uint8_t PKM_20_VERSION_MINOR = '0'; + +typedef uint8_t Byte; + +// This bytes identify an PKM native file. +const Byte FileIdentifier[] = + { + 0x50, + 0x4B, + 0x4D, + 0x20, +}; + +using namespace Pixel; + +// Convert from data type to Dali::Pixel:Format. +const Pixel::Format PKM_FORMAT_TABLE[] = + { + Pixel::Format::COMPRESSED_RGB8_ETC1, ///< 0x0000 + Pixel::Format::COMPRESSED_RGB8_ETC2, ///< 0x0001 + Pixel::Format::COMPRESSED_SRGB8_ETC2, ///< 0x0002 + Pixel::Format::COMPRESSED_RGBA8_ETC2_EAC, ///< 0x0003 + + Pixel::Format::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, ///< 0x0004 + Pixel::Format::COMPRESSED_R11_EAC, ///< 0x0005 + Pixel::Format::COMPRESSED_RG11_EAC, ///< 0x0006 + Pixel::Format::COMPRESSED_SIGNED_R11_EAC, ///< 0x0007 + Pixel::Format::COMPRESSED_SIGNED_RG11_EAC, ///< 0x0008 +}; + +/** + * @brief This struct defines the PKM file header values. From PKM specifications. + * Packed attribute stops the structure from being aligned to compiler defaults + * so we can be sure of reading the whole header from file in one call to fread(). + * Note: members to not conform to coding standards in order to be consistent with PKM spec. + */ +struct PkmFileHeader +{ + uint8_t magic[4]; + uint8_t versionMajor; + uint8_t versionMinor; + uint8_t dataType[2]; // Big Endian + uint8_t extendedWidth[2]; // Big Endian + uint8_t extendedHeight[2]; // Big Endian + uint8_t originalWidth[2]; // Big Endian + uint8_t originalHeight[2]; // Big Endian +} __attribute__((__packed__)); + +/** + * @brief Helper function to get the integer value from Big Endian data array. + * + * @param[in] data 2-byte data + * @return The value of input data said + */ +inline uint32_t GetBigEndianValue(const uint8_t data[2]) +{ + return (static_cast(data[0]) << 8) | data[1]; +} + +/** + * @brief Uses header information to return the respective PKM pixel format. + * + * @param[in] header A populated PkmFileHeader struct + * @return The pixel format, or INVALID if there is no valid pixel format. + */ +Pixel::Format GetPkmPixelFormat(PkmFileHeader& header) +{ + uint32_t pkmFormat = GetBigEndianValue(header.dataType); + if(DALI_LIKELY(pkmFormat < sizeof(PKM_FORMAT_TABLE) / sizeof(PKM_FORMAT_TABLE[0]))) + { + return PKM_FORMAT_TABLE[pkmFormat]; + } + return Pixel::INVALID; +} + +/** + * @brief Internal method to load PKM header info from a file. + * + * @param[in] filePointer The file pointer to the PKM file to read + * @param[out] width The width is output to this value + * @param[out] height The height is output to this value + * @param[out] fileHeader This will be populated with the header data + * @return True if the file is valid, false otherwise + */ +bool LoadPkmHeader(FILE* const filePointer, unsigned int& width, unsigned int& height, PkmFileHeader& fileHeader) +{ + // Pull the bytes of the file header in as a block: + const unsigned int readLength = sizeof(PkmFileHeader); + if(DALI_UNLIKELY(fread(&fileHeader, 1, readLength, filePointer) != readLength)) + { + return false; + } + + // Check the header contains the PKM native file identifier. + bool headerIsValid = memcmp(fileHeader.magic, FileIdentifier, sizeof(fileHeader.magic)) == 0; + if(DALI_UNLIKELY(!headerIsValid)) + { + DALI_LOG_ERROR("File is not a valid PKM native file\n"); + // Return here as otherwise, if not a valid PKM file, we are likely to pick up other header errors spuriously. + return false; + } + + headerIsValid &= (fileHeader.versionMajor == PKM_10_VERSION_MAJOR) || (fileHeader.versionMajor == PKM_20_VERSION_MAJOR); + if(DALI_UNLIKELY(!headerIsValid)) + { + DALI_LOG_ERROR("PKM version doesn't support. file version : %c.%c\n", static_cast(fileHeader.versionMajor), static_cast(fileHeader.versionMinor)); + return false; + } + + // Convert the 2-byte values for width and height to a single resultant value. + width = GetBigEndianValue(fileHeader.originalWidth); + height = GetBigEndianValue(fileHeader.originalHeight); + + // Check image dimensions are within limits. + if(DALI_UNLIKELY((width > MAX_TEXTURE_DIMENSION) || (height > MAX_TEXTURE_DIMENSION))) + { + DALI_LOG_ERROR("PKM file has larger than supported dimensions: %d,%d\n", width, height); + headerIsValid = false; + } + + return headerIsValid; +} + +} // Unnamed namespace. + +// File loading API entry-point: +bool LoadPkmHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height) +{ + PkmFileHeader fileHeader; + return LoadPkmHeader(input.file, width, height, fileHeader); +} + +// File loading API entry-point: +bool LoadBitmapFromPkm(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap) +{ + FILE* const filePointer = input.file; + if(DALI_UNLIKELY(!filePointer)) + { + DALI_LOG_ERROR("Null file handle passed to PKM compressed bitmap file loader.\n"); + return false; + } + + // Load the header info. + PkmFileHeader fileHeader; + unsigned int width, height; + + if(DALI_UNLIKELY(!LoadPkmHeader(filePointer, width, height, fileHeader))) + { + DALI_LOG_ERROR("Could not load PKM Header from file.\n"); + return false; + } + + // Retrieve the pixel format from the PKM header. + Pixel::Format pixelFormat = GetPkmPixelFormat(fileHeader); + if(DALI_UNLIKELY(pixelFormat == Pixel::INVALID)) + { + DALI_LOG_ERROR("No internal pixel format supported for PKM file pixel format.\n"); + return false; + } + + // Retrieve the file size. + if(DALI_UNLIKELY(fseek(filePointer, 0L, SEEK_END))) + { + DALI_LOG_ERROR("Could not seek through file.\n"); + return false; + } + + off_t fileSize = ftell(filePointer); + if(DALI_UNLIKELY(fileSize == -1L)) + { + DALI_LOG_ERROR("Could not determine PKM file size.\n"); + return false; + } + + if(DALI_UNLIKELY(fseek(filePointer, sizeof(PkmFileHeader), SEEK_SET))) + { + DALI_LOG_ERROR("Could not seek through file.\n"); + return false; + } + + // Data size is file size - header size. + size_t imageByteCount = fileSize - sizeof(PkmFileHeader); + + // Sanity-check the image data is not too large: + if(DALI_UNLIKELY(imageByteCount > MAX_IMAGE_DATA_SIZE)) + { + DALI_LOG_ERROR("PKM file has too large image-data field.\n"); + return false; + } + + // allocate pixel data + auto* pixels = static_cast(malloc(imageByteCount)); + + if(DALI_UNLIKELY(pixels == nullptr)) + { + DALI_LOG_ERROR("Buffer allocation failed. (required memory : %zu byte)\n", imageByteCount); + return false; + } + + // Create bitmap who will use allocated buffer. + const auto& bitmapInternal = Internal::Adaptor::PixelBuffer::New(pixels, imageByteCount, width, height, 0, pixelFormat); + bitmap = Dali::Devel::PixelBuffer(bitmapInternal.Get()); + + // Load the image data. + const size_t bytesRead = fread(pixels, 1, imageByteCount, filePointer); + + // Check the size of loaded data is what we expected. + if(DALI_UNLIKELY(bytesRead != imageByteCount)) + { + DALI_LOG_ERROR("Read of image pixel data failed. (required image bytes : %zu, actual read from file : %zu\n", imageByteCount, bytesRead); + return false; + } + + return true; +} + +} // namespace TizenPlatform + +} // namespace Dali diff --git a/dali/internal/imaging/common/loader-pkm.h b/dali/internal/imaging/common/loader-pkm.h new file mode 100644 index 0000000..ea17edb --- /dev/null +++ b/dali/internal/imaging/common/loader-pkm.h @@ -0,0 +1,63 @@ +#ifndef DALI_TIZEN_PLATFORM_LOADER_PKM_H +#define DALI_TIZEN_PLATFORM_LOADER_PKM_H + +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +namespace Dali +{ +namespace Devel +{ +class PixelBuffer; +} + +namespace TizenPlatform +{ +class ResourceLoadingClient; + +namespace Pkm +{ +const unsigned char MAGIC_BYTE_1 = 0x50; +const unsigned char MAGIC_BYTE_2 = 0x4B; +} // namespace Pkm + +/** + * Loads a compressed bitmap image from a PKM file without decoding it. + * This function checks the header first + * and if it is not a PKM file, or the header contents are invalid, it will return a failure. + * @param[in] input Information about the input image (including file pointer) + * @param[out] bitmap The bitmap class where the decoded image will be stored + * @return True if file loaded successfully, false otherwise + */ +bool LoadBitmapFromPkm(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap); + +/** + * Loads the header of a PKM file and fills in the width and height appropriately. + * @param[in] input Information about the input image (including file pointer) + * @param[out] width Is set with the width of the image + * @param[out] height Is set with the height of the image + * @return True if the header was read successfully, false otherwise + */ +bool LoadPkmHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height); + +} // namespace TizenPlatform + +} // namespace Dali + +#endif // DALI_TIZEN_PLATFORM_LOADER_PKM_H diff --git a/dali/internal/imaging/common/pixel-buffer-impl.cpp b/dali/internal/imaging/common/pixel-buffer-impl.cpp index ffdb355..31b7f0f 100644 --- a/dali/internal/imaging/common/pixel-buffer-impl.cpp +++ b/dali/internal/imaging/common/pixel-buffer-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 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. @@ -37,7 +37,6 @@ namespace Adaptor { namespace { - #if defined(DEBUG_ENABLED) Debug::Filter* gPixelBufferFilter = Debug::Filter::New(Debug::NoLogging, false, "DALI_LOG_PIXEL_BUFFER_SIZE"); #endif @@ -474,12 +473,12 @@ void PixelBuffer::ApplyGaussianBlur(const float blurRadius) void PixelBuffer::MultiplyColorByAlpha() { - auto bytesPerPixel = Pixel::GetBytesPerPixel(mPixelFormat); - // Compressed textures have unknown size of the pixel. Alpha premultiplication // must be skipped in such case - if(Pixel::GetBytesPerPixel(mPixelFormat) && Pixel::HasAlpha(mPixelFormat)) + if(!Pixel::IsCompressed(mPixelFormat) && Pixel::HasAlpha(mPixelFormat)) { + auto bytesPerPixel = Pixel::GetBytesPerPixel(mPixelFormat); + uint8_t* pixel = mBuffer; const uint32_t strideBytes = mStride * bytesPerPixel; const uint32_t widthBytes = mWidth * bytesPerPixel; diff --git a/dali/internal/imaging/file.list b/dali/internal/imaging/file.list index 6398784..cab4cea 100644 --- a/dali/internal/imaging/file.list +++ b/dali/internal/imaging/file.list @@ -16,6 +16,7 @@ SET( adaptor_imaging_common_src_files ${adaptor_imaging_dir}/common/loader-ico.cpp ${adaptor_imaging_dir}/common/loader-jpeg-turbo.cpp ${adaptor_imaging_dir}/common/loader-ktx.cpp + ${adaptor_imaging_dir}/common/loader-pkm.cpp ${adaptor_imaging_dir}/common/loader-png.cpp ${adaptor_imaging_dir}/common/loader-wbmp.cpp ${adaptor_imaging_dir}/common/loader-webp.cpp -- 2.7.4