PngFile.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Atom/Utils/PngFile.h>
  9. #include <png.h>
  10. #include <AzCore/IO/SystemFile.h>
  11. namespace AZ
  12. {
  13. namespace Utils
  14. {
  15. namespace
  16. {
  17. void PngImage_user_error_fn(png_structp png_ptr, png_const_charp error_msg)
  18. {
  19. PngFile::ErrorHandler* errorHandler = reinterpret_cast<PngFile::ErrorHandler*>(png_get_error_ptr(png_ptr));
  20. (*errorHandler)(error_msg);
  21. }
  22. void PngImage_user_warning_fn(png_structp /*png_ptr*/, [[maybe_unused]] png_const_charp warning_msg)
  23. {
  24. AZ_Warning("PngFile", false, "%s", warning_msg);
  25. }
  26. }
  27. PngFile PngFile::Create(const RHI::Size& size, RHI::Format format, AZStd::span<const uint8_t> data, ErrorHandler errorHandler)
  28. {
  29. return Create(size, format, AZStd::vector<uint8_t>{data.begin(), data.end()}, errorHandler);
  30. }
  31. PngFile PngFile::Create(const RHI::Size& size, RHI::Format format, AZStd::vector<uint8_t>&& data, ErrorHandler errorHandler)
  32. {
  33. if (!errorHandler)
  34. {
  35. errorHandler = [](const char* message) { DefaultErrorHandler(message); };
  36. }
  37. PngFile image;
  38. if (RHI::Format::R8G8B8A8_UNORM == format)
  39. {
  40. if (size.m_width * size.m_height * 4 == data.size())
  41. {
  42. image.m_width = size.m_width;
  43. image.m_height = size.m_height;
  44. image.m_bitDepth = 8;
  45. image.m_bufferFormat = PngFile::Format::RGBA;
  46. image.m_buffer = AZStd::move(data);
  47. }
  48. else
  49. {
  50. errorHandler("Invalid arguments. Buffer size does not match the image dimensions.");
  51. }
  52. }
  53. else
  54. {
  55. errorHandler(AZStd::string::format("Cannot create PngFile with unsupported format %s", AZ::RHI::ToString(format)).c_str());
  56. }
  57. return image;
  58. }
  59. PngFile PngFile::Load(const char* path, LoadSettings loadSettings)
  60. {
  61. if (!loadSettings.m_errorHandler)
  62. {
  63. loadSettings.m_errorHandler = [path](const char* message)
  64. {
  65. DefaultErrorHandler(AZStd::string::format("Could not load file '%s'. %s", path, message).c_str());
  66. };
  67. }
  68. AZ::IO::SystemFileStream fileLoadStream(path, AZ::IO::OpenMode::ModeRead);
  69. if (!fileLoadStream.IsOpen())
  70. {
  71. loadSettings.m_errorHandler("Cannot open file.");
  72. return {};
  73. }
  74. auto pngFile = LoadInternal(fileLoadStream, loadSettings);
  75. return pngFile;
  76. }
  77. PngFile PngFile::LoadFromBuffer(AZStd::span<const uint8_t> data, LoadSettings loadSettings)
  78. {
  79. if (!loadSettings.m_errorHandler)
  80. {
  81. loadSettings.m_errorHandler = [](const char* message)
  82. {
  83. DefaultErrorHandler(AZStd::string::format("Could not load Png from buffer. %s", message).c_str());
  84. };
  85. }
  86. if (data.empty())
  87. {
  88. loadSettings.m_errorHandler("Buffer is empty.");
  89. return {};
  90. }
  91. AZ::IO::MemoryStream memStream(data.data(), data.size());
  92. return LoadInternal(memStream, loadSettings);
  93. }
  94. PngFile PngFile::LoadInternal(AZ::IO::GenericStream& dataStream, LoadSettings loadSettings)
  95. {
  96. // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 3
  97. // Verify that we've passed in a valid data stream.
  98. if (!dataStream.IsOpen() || !dataStream.CanRead())
  99. {
  100. loadSettings.m_errorHandler("Data stream isn't valid.");
  101. return {};
  102. }
  103. png_byte header[HeaderSize] = {};
  104. size_t headerBytesRead = 0;
  105. // This is the one I/O read that occurs outside of the png library, so either read from the file or the buffer and
  106. // verify the results.
  107. headerBytesRead = dataStream.Read(HeaderSize, header);
  108. if (headerBytesRead != HeaderSize)
  109. {
  110. loadSettings.m_errorHandler("Invalid png header.");
  111. return {};
  112. }
  113. bool isPng = !png_sig_cmp(header, 0, HeaderSize);
  114. if (!isPng)
  115. {
  116. loadSettings.m_errorHandler("Invalid png header.");
  117. return {};
  118. }
  119. png_voidp user_error_ptr = &loadSettings.m_errorHandler;
  120. png_error_ptr user_error_fn = PngImage_user_error_fn;
  121. png_error_ptr user_warning_fn = PngImage_user_warning_fn;
  122. png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn);
  123. if (!png_ptr)
  124. {
  125. loadSettings.m_errorHandler("png_create_read_struct failed.");
  126. return {};
  127. }
  128. png_infop info_ptr = png_create_info_struct(png_ptr);
  129. if (!info_ptr)
  130. {
  131. png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
  132. loadSettings.m_errorHandler("png_create_info_struct failed.");
  133. return {};
  134. }
  135. png_infop end_info = png_create_info_struct(png_ptr);
  136. if (!end_info)
  137. {
  138. png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
  139. loadSettings.m_errorHandler("png_create_info_struct failed.");
  140. return {};
  141. }
  142. // Disables "interaction between '_setjmp' and C++ object destruction is non-portable".
  143. // See https://docs.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-160
  144. AZ_PUSH_DISABLE_WARNING(4611, "-Wunknown-warning-option")
  145. if (setjmp(png_jmpbuf(png_ptr)))
  146. {
  147. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
  148. // We don't report an error message here because the user_error_fn should have done that already.
  149. return {};
  150. }
  151. AZ_POP_DISABLE_WARNING
  152. auto genericStreamReader = [](png_structp pngPtr, png_bytep data, png_size_t length)
  153. {
  154. // Here we get our IO pointer back from the read struct.
  155. // This should be the GenericStream pointer we passed to the png_set_read_fn() function.
  156. png_voidp ioPtr = png_get_io_ptr(pngPtr);
  157. if (ioPtr != nullptr)
  158. {
  159. AZ::IO::GenericStream* genericStream = static_cast<AZ::IO::GenericStream*>(ioPtr);
  160. genericStream->Read(length, data);
  161. }
  162. };
  163. png_set_read_fn(png_ptr, &dataStream, genericStreamReader);
  164. png_set_sig_bytes(png_ptr, HeaderSize);
  165. png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, NULL, 0);
  166. // To keep things simple for now we limit all images to RGB and RGBA, 8 bits per channel
  167. int png_transforms = PNG_TRANSFORM_PACKING | // Expand 1, 2 and 4-bit samples to bytes
  168. PNG_TRANSFORM_STRIP_16 | // Reduce 16 bit samples to 8 bits
  169. PNG_TRANSFORM_GRAY_TO_RGB;
  170. if (loadSettings.m_stripAlpha)
  171. {
  172. png_transforms |= PNG_TRANSFORM_STRIP_ALPHA;
  173. }
  174. png_read_png(png_ptr, info_ptr, png_transforms, NULL);
  175. // Note that libpng will allocate row_pointers for us. If we want to manage the memory ourselves, we need to call png_set_rows.
  176. // In that case we would have to use the low level interface: png_read_info, png_read_image, and png_read_end.
  177. png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr);
  178. PngFile pngFile;
  179. int colorType = 0;
  180. png_get_IHDR(png_ptr, info_ptr, &pngFile.m_width, &pngFile.m_height, &pngFile.m_bitDepth, &colorType, NULL, NULL, NULL);
  181. uint32_t bytesPerPixel = 0;
  182. switch (colorType)
  183. {
  184. case PNG_COLOR_TYPE_RGB:
  185. pngFile.m_bufferFormat = PngFile::Format::RGB;
  186. bytesPerPixel = 3;
  187. break;
  188. case PNG_COLOR_TYPE_RGBA:
  189. pngFile.m_bufferFormat = PngFile::Format::RGBA;
  190. bytesPerPixel = 4;
  191. break;
  192. case PNG_COLOR_TYPE_PALETTE:
  193. // Handles cases where the image uses 1, 2, or 4 bit samples.
  194. // Note bytesPerPixel is 3 because we use PNG_TRANSFORM_PACKING
  195. pngFile.m_bufferFormat = PngFile::Format::RGB;
  196. bytesPerPixel = 3;
  197. break;
  198. default:
  199. AZ_Assert(false, "The png transforms should have ensured a pixel format of RGB or RGBA, 8 bits per channel");
  200. png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
  201. loadSettings.m_errorHandler("Unsupported pixel format.");
  202. return {};
  203. }
  204. // In the future we could use the low-level interface to avoid copying the image (and provide progress callbacks)
  205. pngFile.m_buffer.set_capacity(pngFile.m_width * pngFile.m_height * bytesPerPixel);
  206. for (uint32_t rowIndex = 0; rowIndex < pngFile.m_height; ++rowIndex)
  207. {
  208. png_bytep row = row_pointers[rowIndex];
  209. pngFile.m_buffer.insert(pngFile.m_buffer.end(), row, row + (pngFile.m_width * bytesPerPixel));
  210. }
  211. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
  212. return pngFile;
  213. }
  214. bool PngFile::Save(const char* path, SaveSettings saveSettings)
  215. {
  216. if (!saveSettings.m_errorHandler)
  217. {
  218. saveSettings.m_errorHandler = [path](const char* message) { DefaultErrorHandler(AZStd::string::format("Could not save file '%s'. %s", path, message).c_str()); };
  219. }
  220. if (!IsValid())
  221. {
  222. saveSettings.m_errorHandler("This PngFile is invalid.");
  223. return false;
  224. }
  225. // For documentation of this code, see http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf chapter 4
  226. FILE* fp = NULL;
  227. azfopen(&fp, path, "wb"); // return type differs across platforms so can't do inside if
  228. if (!fp)
  229. {
  230. saveSettings.m_errorHandler("Cannot open file.");
  231. return false;
  232. }
  233. png_voidp user_error_ptr = &saveSettings.m_errorHandler;
  234. png_error_ptr user_error_fn = PngImage_user_error_fn;
  235. png_error_ptr user_warning_fn = PngImage_user_warning_fn;
  236. png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, user_error_ptr, user_error_fn, user_warning_fn);
  237. if (!png_ptr)
  238. {
  239. fclose(fp);
  240. saveSettings.m_errorHandler("png_create_write_struct failed.");
  241. return false;
  242. }
  243. png_infop info_ptr = png_create_info_struct(png_ptr);
  244. if (!info_ptr)
  245. {
  246. png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
  247. fclose(fp);
  248. saveSettings.m_errorHandler("png_destroy_write_struct failed.");
  249. return false;
  250. }
  251. 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
  252. if (setjmp(png_jmpbuf(png_ptr)))
  253. {
  254. png_destroy_write_struct(&png_ptr, &info_ptr);
  255. fclose(fp);
  256. // We don't report an error message here because the user_error_fn should have done that already.
  257. return false;
  258. }
  259. AZ_POP_DISABLE_WARNING
  260. png_init_io(png_ptr, fp);
  261. int colorType = 0;
  262. if (saveSettings.m_stripAlpha || m_bufferFormat == PngFile::Format::RGB)
  263. {
  264. colorType = PNG_COLOR_TYPE_RGB;
  265. }
  266. else
  267. {
  268. colorType = PNG_COLOR_TYPE_RGBA;
  269. }
  270. int transforms = PNG_TRANSFORM_IDENTITY;
  271. if (saveSettings.m_stripAlpha && m_bufferFormat == PngFile::Format::RGBA)
  272. {
  273. transforms |= PNG_TRANSFORM_STRIP_FILLER_AFTER;
  274. }
  275. png_set_IHDR(png_ptr, info_ptr, m_width, m_height, m_bitDepth, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
  276. png_set_compression_level(png_ptr, saveSettings.m_compressionLevel);
  277. const uint32_t bytesPerPixel = (m_bufferFormat == PngFile::Format::RGB) ? 3 : 4;
  278. AZStd::vector<uint8_t*> rows;
  279. rows.reserve(m_height);
  280. for (uint32_t i = 0; i < m_height; ++i)
  281. {
  282. rows.push_back(m_buffer.begin() + m_width * bytesPerPixel * i);
  283. }
  284. png_set_rows(png_ptr, info_ptr, rows.begin());
  285. png_write_png(png_ptr, info_ptr, transforms, NULL);
  286. png_destroy_write_struct(&png_ptr, &info_ptr);
  287. fclose(fp);
  288. return true;
  289. }
  290. void PngFile::DefaultErrorHandler([[maybe_unused]] const char* message)
  291. {
  292. AZ_Error("PngFile", false, "%s", message);
  293. }
  294. bool PngFile::IsValid() const
  295. {
  296. return
  297. !m_buffer.empty() &&
  298. m_width > 0 &&
  299. m_height > 0 &&
  300. m_bitDepth > 0;
  301. }
  302. AZStd::vector<uint8_t>&& PngFile::TakeBuffer()
  303. {
  304. return AZStd::move(m_buffer);
  305. }
  306. } // namespace Utils
  307. }// namespace AZ