Add support for loading Png data directly from a memory buffer. (#6527)

* Add support for loading Png data directly from a memory buffer.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
(cherry picked from commit 6193dcf603d3c16ee193e1e6dc868bd6d04f89bc)

* Addressed PR feedback.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Addressed PR feedback.
Simplified LoadInternal() by having it always use a GenericStream.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
monroegm-disable-blank-issue-2
Mike Balfour 4 years ago committed by GitHub
parent 44744d95b0
commit 845525a722
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,6 +7,7 @@
*/
#pragma once
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/unordered_map.h>
@ -53,6 +54,9 @@ namespace AZ
//! @return the loaded PngFile or an invalid PngFile if there was an error.
static PngFile Load(const char* path, LoadSettings loadSettings = {});
//! @return the loaded PngFile or an invalid PngFile if there was an error.
static PngFile LoadFromBuffer(AZStd::array_view<uint8_t> data, LoadSettings loadSettings = {});
//! Create a PngFile from an RHI data buffer.
//! @param size the dimensions of the image (m_depth is not used, assumed to be 1)
//! @param format indicates the pixel format represented by @data. Only a limited set of formats are supported, see implementation.
@ -83,10 +87,12 @@ namespace AZ
private:
AZ_DEFAULT_COPY(PngFile)
static const int HeaderSize = 8;
static const int HeaderSize = 8;
static void DefaultErrorHandler(const char* message);
static PngFile LoadInternal(AZ::IO::GenericStream& dataStream, LoadSettings loadSettings);
uint32_t m_width = 0;
uint32_t m_height = 0;
int32_t m_bitDepth = 0;

@ -8,6 +8,7 @@
#include <Atom/Utils/PngFile.h>
#include <png.h>
#include <AzCore/IO/SystemFile.h>
namespace AZ
{
@ -68,24 +69,66 @@ namespace AZ
{
if (!loadSettings.m_errorHandler)
{
loadSettings.m_errorHandler = [path](const char* message) { DefaultErrorHandler(AZStd::string::format("Could not load file '%s'. %s", path, message).c_str()); };
loadSettings.m_errorHandler = [path](const char* message)
{
DefaultErrorHandler(AZStd::string::format("Could not load file '%s'. %s", path, message).c_str());
};
}
AZ::IO::SystemFile file;
file.Open(path, AZ::IO::SystemFile::SF_OPEN_READ_ONLY);
if (!file.IsOpen())
{
loadSettings.m_errorHandler("Cannot open file.");
return {};
}
constexpr bool StreamOwnsFilePointer = true;
AZ::IO::SystemFileStream fileLoadStream(&file, StreamOwnsFilePointer);
auto pngFile = LoadInternal(fileLoadStream, loadSettings);
return pngFile;
}
PngFile PngFile::LoadFromBuffer(AZStd::array_view<uint8_t> data, LoadSettings loadSettings)
{
if (!loadSettings.m_errorHandler)
{
loadSettings.m_errorHandler = [](const char* message)
{
DefaultErrorHandler(AZStd::string::format("Could not load Png from buffer. %s", message).c_str());
};
}
if (data.empty())
{
loadSettings.m_errorHandler("Buffer is empty.");
return {};
}
AZ::IO::MemoryStream memStream(data.data(), data.size());
return LoadInternal(memStream, loadSettings);
}
PngFile PngFile::LoadInternal(AZ::IO::GenericStream& dataStream, LoadSettings loadSettings)
{
// For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 3
FILE* fp = NULL;
azfopen(&fp, path, "rb"); // return type differs across platforms so can't do inside if
if (!fp)
// Verify that we've passed in a valid data stream.
if (!dataStream.IsOpen() || !dataStream.CanRead())
{
loadSettings.m_errorHandler("Cannot open file.");
loadSettings.m_errorHandler("Data stream isn't valid.");
return {};
}
png_byte header[HeaderSize] = {};
size_t headerBytesRead = 0;
if (fread(header, 1, HeaderSize, fp) != HeaderSize)
// This is the one I/O read that occurs outside of the png library, so either read from the file or the buffer and
// verify the results.
headerBytesRead = dataStream.Read(HeaderSize, header);
if (headerBytesRead != HeaderSize)
{
fclose(fp);
loadSettings.m_errorHandler("Invalid png header.");
return {};
}
@ -93,7 +136,6 @@ namespace AZ
bool isPng = !png_sig_cmp(header, 0, HeaderSize);
if (!isPng)
{
fclose(fp);
loadSettings.m_errorHandler("Invalid png header.");
return {};
}
@ -105,7 +147,6 @@ namespace AZ
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn);
if (!png_ptr)
{
fclose(fp);
loadSettings.m_errorHandler("png_create_read_struct failed.");
return {};
}
@ -114,7 +155,6 @@ namespace AZ
if (!info_ptr)
{
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
fclose(fp);
loadSettings.m_errorHandler("png_create_info_struct failed.");
return {};
}
@ -123,22 +163,35 @@ namespace AZ
if (!end_info)
{
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
fclose(fp);
loadSettings.m_errorHandler("png_create_info_struct failed.");
return {};
}
AZ_PUSH_DISABLE_WARNING(4611, "-Wunknown-warning-option") // Disables "interaction between '_setjmp' and C++ object destruction is non-portable". See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160
// Disables "interaction between '_setjmp' and C++ object destruction is non-portable".
// See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160
AZ_PUSH_DISABLE_WARNING(4611, "-Wunknown-warning-option")
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
fclose(fp);
// We don't report an error message here because the user_error_fn should have done that already.
return {};
}
AZ_POP_DISABLE_WARNING
png_init_io(png_ptr, fp);
auto genericStreamReader = [](png_structp pngPtr, png_bytep data, png_size_t length)
{
// Here we get our IO pointer back from the read struct.
// This should be the GenericStream pointer we passed to the png_set_read_fn() function.
png_voidp ioPtr = png_get_io_ptr(pngPtr);
if (ioPtr != nullptr)
{
AZ::IO::GenericStream* genericStream = static_cast<AZ::IO::GenericStream*>(ioPtr);
genericStream->Read(length, data);
}
};
png_set_read_fn(png_ptr, &dataStream, genericStreamReader);
png_set_sig_bytes(png_ptr, HeaderSize);
@ -187,7 +240,6 @@ AZ_POP_DISABLE_WARNING
default:
AZ_Assert(false, "The png transforms should have ensured a pixel format of RGB or RGBA, 8 bits per channel");
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
fclose(fp);
loadSettings.m_errorHandler("Unsupported pixel format.");
return {};
}
@ -201,7 +253,6 @@ AZ_POP_DISABLE_WARNING
}
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
fclose(fp);
return pngFile;
}
@ -322,3 +373,4 @@ AZ_POP_DISABLE_WARNING
} // namespace Utils
}// namespace AZ

@ -311,4 +311,66 @@ namespace UnitTest
EXPECT_TRUE(gotErrorMessage.find("PngFile is invalid") != AZStd::string::npos);
EXPECT_FALSE(AZ::IO::FileIOBase::GetInstance()->Exists(m_tempPngFilePath.c_str()));
}
}
TEST_F(PngFileTests, LoadRgbFromMemoryBuffer)
{
// This is an in-memory copy of the ColorChart_rgb.png test file.
AZStd::fixed_vector<uint8_t, 126> pngBuffer =
{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02,
0x08, 0x02, 0x00, 0x00, 0x00, 0x12, 0x16, 0xf1,
0x4d, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00,
0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 0x00,
0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7,
0x6f, 0xa8, 0x64, 0x00, 0x00, 0x00, 0x13, 0x49,
0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0xf8, 0xcf,
0xc0, 0x00, 0xc1, 0x4c, 0x10, 0xea, 0x3f, 0x03,
0x03, 0x00, 0x3b, 0xec, 0x05, 0xfd, 0x6a, 0x50,
0x07, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
PngFile image = PngFile::LoadFromBuffer(pngBuffer);
EXPECT_TRUE(image.IsValid());
EXPECT_EQ(image.GetBufferFormat(), PngFile::Format::RGB);
EXPECT_EQ(image.GetWidth(), 3);
EXPECT_EQ(image.GetHeight(), 2);
EXPECT_EQ(image.GetBuffer().size(), 18);
EXPECT_EQ(Color3(image.GetBuffer().begin() + 0), Color3(255u, 0u, 0u));
EXPECT_EQ(Color3(image.GetBuffer().begin() + 3), Color3(0u, 255u, 0u));
EXPECT_EQ(Color3(image.GetBuffer().begin() + 6), Color3(0u, 0u, 255u));
EXPECT_EQ(Color3(image.GetBuffer().begin() + 9), Color3(255u, 255u, 0u));
EXPECT_EQ(Color3(image.GetBuffer().begin() + 12), Color3(0u, 255u, 255u));
EXPECT_EQ(Color3(image.GetBuffer().begin() + 15), Color3(255u, 0u, 255u));
}
TEST_F(PngFileTests, ErrorCannotLoadEmptyMemoryBuffer)
{
AZStd::vector<uint8_t> pngBuffer;
AZStd::string gotErrorMessage;
PngFile::LoadSettings loadSettings;
loadSettings.m_errorHandler = [&gotErrorMessage](const char* errorMessage)
{
gotErrorMessage = errorMessage;
};
PngFile image = PngFile::LoadFromBuffer(pngBuffer, loadSettings);
EXPECT_FALSE(image.IsValid());
EXPECT_TRUE(gotErrorMessage.find("Buffer is empty") != AZStd::string::npos);
}
} // namespace UnitTest

Loading…
Cancel
Save