Support *.pkm format file decode 86/297286/4
authorEunki, Hong <eunkiki.hong@samsung.com>
Wed, 16 Aug 2023 08:37:08 +0000 (17:37 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Fri, 18 Aug 2023 01:24:53 +0000 (10:24 +0900)
Let we support .pkm format file decode, who use ETC1 and ETC2 pixel format.

Change-Id: Ib1d7c0b3653f31fd6a4bb1fd378b7fe0cdcbb3a2
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
dali/internal/imaging/common/image-loader.cpp
dali/internal/imaging/common/loader-pkm.cpp [new file with mode: 0644]
dali/internal/imaging/common/loader-pkm.h [new file with mode: 0644]
dali/internal/imaging/common/pixel-buffer-impl.cpp
dali/internal/imaging/file.list

index 5f6623b..a88270a 100644 (file)
@@ -28,6 +28,7 @@
 #include <dali/internal/imaging/common/loader-ico.h>
 #include <dali/internal/imaging/common/loader-jpeg.h>
 #include <dali/internal/imaging/common/loader-ktx.h>
+#include <dali/internal/imaging/common/loader-pkm.h>
 #include <dali/internal/imaging/common/loader-png.h>
 #include <dali/internal/imaging/common/loader-wbmp.h>
 #include <dali/internal/imaging/common/loader-webp.h>
@@ -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 (file)
index 0000000..71b1404
--- /dev/null
@@ -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 <dali/internal/imaging/common/loader-pkm.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/pixel-buffer.h>
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/images/pixel.h>
+#include <cstring> ///< for memcmp
+
+// INTERNAL INCLUDES
+#include <dali/internal/imaging/common/pixel-buffer-impl.h> ///< 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<uint32_t>(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<char>(fileHeader.versionMajor), static_cast<char>(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<uint8_t*>(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 (file)
index 0000000..ea17edb
--- /dev/null
@@ -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 <dali/devel-api/adaptor-framework/image-loader-input.h>
+
+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
index ffdb355..31b7f0f 100644 (file)
@@ -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;
index 6398784..cab4cea 100644 (file)
@@ -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